diff --git a/1.conf b/1.conf new file mode 100644 index 0000000..6e7a5e3 --- /dev/null +++ b/1.conf @@ -0,0 +1,67 @@ +server { + listen 80; + server_name api.hifast.biz 4d3vsw8.88xgaen.hifast.biz; + + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl http2; + server_name api.hifast.biz; + client_max_body_size 150M; + + ssl_certificate /etc/letsencrypt/live/api.hifast.biz/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/api.hifast.biz/privkey.pem; + # 安全头 + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + + location / { + proxy_pass http://127.0.0.1:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} + +# de99e242子域名指向3001 (管理界面) +server { + listen 443 ssl http2; + server_name 4d3vsw8.88xgaen.hifast.biz; + client_max_body_size 150M; + + ssl_certificate /etc/letsencrypt/live/4d3vsw8.88xgaen.hifast.biz/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/4d3vsw8.88xgaen.hifast.biz/privkey.pem; + + # 安全头 + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + + # Gzip压缩 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json image/svg+xml; + + location ^~ / { + proxy_pass http://127.0.0.1:3001; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header REMOTE-HOST $remote_addr; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $http_connection; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Port $server_port; + proxy_http_version 1.1; + add_header X-Cache $upstream_cache_status; + add_header Cache-Control no-cache; + proxy_ssl_server_name off; + proxy_ssl_name $proxy_host; + } +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 6aabce2..74d41df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ FROM golang:alpine AS builder LABEL stage=gobuilder ARG TARGETARCH +ARG VERSION ENV CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} # Combine apk commands into one to reduce layer size @@ -18,8 +19,9 @@ RUN go mod download # Copy the rest of the application code COPY . . -# Build the binary with optimization flags to reduce binary size -RUN go build -ldflags="-s -w" -o /app/ppanel ppanel.go +# Build the binary with version and build time +RUN BUILD_TIME=$(date -u +"%Y-%m-%d %H:%M:%S") && \ + go build -ldflags="-s -w -X 'github.com/perfect-panel/server/pkg/constant.Version=${VERSION}' -X 'github.com/perfect-panel/server/pkg/constant.BuildTime=${BUILD_TIME}'" -o /app/ppanel ppanel.go # Final minimal image FROM scratch @@ -34,11 +36,11 @@ ENV TZ=Asia/Shanghai WORKDIR /app COPY --from=builder /app/ppanel /app/ppanel -COPY --from=builder /etc /app/etc +COPY --from=builder /build/etc /app/etc # Expose the port (optional) EXPOSE 8080 # Specify entry point ENTRYPOINT ["/app/ppanel"] -CMD ["run", "--config", "etc/ppanel.yaml"] +CMD ["run", "--config", "etc/ppanel.yaml"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..47b9849 --- /dev/null +++ b/Makefile @@ -0,0 +1,84 @@ +NAME="ppanel-server" +BINDIR=bin +VERSION=$(shell git describe --tags || echo "unknown version") +BUILDTIME=$(shell date -u) +GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/perfect-panel/server/pkg/constant.Version=$(VERSION)" \ + -X "github.com/perfect-panel/server/pkg/constant.BuildTime=$(BUILDTIME)" \ + -w -s -buildid=' + +PLATFORM_LIST = \ + darwin-amd64 \ + darwin-amd64-v3 \ + darwin-arm64 \ + linux-386 \ + linux-amd64 \ + linux-amd64-v3 \ + linux-armv5 \ + linux-armv6 \ + linux-armv7 \ + linux-arm64 \ + +WINDOWS_ARCH_LIST = \ + windows-386 \ + windows-amd64 \ + windows-amd64-v3 \ + windows-arm64 \ + windows-armv7 + +all: linux-amd64 darwin-amd64 windows-amd64 # Most used + +darwin-amd64: + GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +darwin-amd64-v3: + GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +darwin-arm64: + GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-386: + GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-amd64: + GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-amd64-v3: + GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-armv5: + GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-armv6: + GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-armv7: + GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-arm64: + GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +windows-386: + GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe + +windows-amd64: + GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe + +windows-amd64-v3: + GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe + +windows-arm64: + GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe + +windows-armv7: + GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe + + +gz_releases=$(addsuffix .gz, $(PLATFORM_LIST)) +zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST)) + +$(gz_releases): %.gz : % + chmod +x $(BINDIR)/$(NAME)-$(basename $@) + gzip -f -S -$(VERSION).gz $(BINDIR)/$(NAME)-$(basename $@) + +$(zip_releases): %.zip : % + zip -m -j $(BINDIR)/$(NAME)-$(basename $@)-$(VERSION).zip $(BINDIR)/$(NAME)-$(basename $@).exe diff --git a/adapter/adapter.go b/adapter/adapter.go new file mode 100644 index 0000000..1acc674 --- /dev/null +++ b/adapter/adapter.go @@ -0,0 +1,140 @@ +package adapter + +import ( + "strings" + + "github.com/perfect-panel/server/internal/model/node" + "github.com/perfect-panel/server/pkg/logger" +) + +type Adapter struct { + SiteName string // 站点名称 + Servers []*node.Node // 服务器列表 + UserInfo User // 用户信息 + ClientTemplate string // 客户端配置模板 + OutputFormat string // 输出格式,默认是 base64 + SubscribeName string // 订阅名称 +} + +type Option func(*Adapter) + +// WithServers 设置服务器列表 +func WithServers(servers []*node.Node) Option { + return func(opts *Adapter) { + opts.Servers = servers + } +} + +// WithUserInfo 设置用户信息 +func WithUserInfo(user User) Option { + return func(opts *Adapter) { + opts.UserInfo = user + } +} + +// WithOutputFormat 设置输出格式 +func WithOutputFormat(format string) Option { + return func(opts *Adapter) { + opts.OutputFormat = format + } +} + +// WithSiteName 设置站点名称 +func WithSiteName(name string) Option { + return func(opts *Adapter) { + opts.SiteName = name + } +} + +// WithSubscribeName 设置订阅名称 +func WithSubscribeName(name string) Option { + return func(opts *Adapter) { + opts.SubscribeName = name + } +} + +func NewAdapter(tpl string, opts ...Option) *Adapter { + adapter := &Adapter{ + Servers: []*node.Node{}, + UserInfo: User{}, + ClientTemplate: tpl, + OutputFormat: "base64", // 默认输出格式 + } + + for _, opt := range opts { + opt(adapter) + } + + return adapter +} + +func (adapter *Adapter) Client() (*Client, error) { + client := &Client{ + SiteName: adapter.SiteName, + SubscribeName: adapter.SubscribeName, + ClientTemplate: adapter.ClientTemplate, + OutputFormat: adapter.OutputFormat, + Proxies: []Proxy{}, + UserInfo: adapter.UserInfo, + } + + proxies, err := adapter.Proxies(adapter.Servers) + if err != nil { + return nil, err + } + client.Proxies = proxies + return client, nil +} + +func (adapter *Adapter) Proxies(servers []*node.Node) ([]Proxy, error) { + var proxies []Proxy + + for _, item := range servers { + if item.Server == nil { + logger.Errorf("[Adapter] Server is nil for node ID: %d", item.Id) + continue + } + protocols, err := item.Server.UnmarshalProtocols() + if err != nil { + logger.Errorf("[Adapter] Unmarshal Protocols error: %s; server id : %d", err.Error(), item.ServerId) + continue + } + for _, protocol := range protocols { + if protocol.Type == item.Protocol { + proxies = append(proxies, Proxy{ + Sort: item.Sort, + Name: item.Name, + Server: item.Address, + Port: item.Port, + Type: item.Protocol, + Tags: strings.Split(item.Tags, ","), + Security: protocol.Security, + SNI: protocol.SNI, + AllowInsecure: protocol.AllowInsecure, + Fingerprint: protocol.Fingerprint, + RealityServerAddr: protocol.RealityServerAddr, + RealityServerPort: protocol.RealityServerPort, + RealityPrivateKey: protocol.RealityPrivateKey, + RealityPublicKey: protocol.RealityPublicKey, + RealityShortId: protocol.RealityShortId, + Transport: protocol.Transport, + Host: protocol.Host, + Path: protocol.Path, + ServiceName: protocol.ServiceName, + Method: protocol.Cipher, + ServerKey: protocol.ServerKey, + Flow: protocol.Flow, + HopPorts: protocol.HopPorts, + HopInterval: protocol.HopInterval, + ObfsPassword: protocol.ObfsPassword, + DisableSNI: protocol.DisableSNI, + ReduceRtt: protocol.ReduceRtt, + UDPRelayMode: protocol.UDPRelayMode, + CongestionController: protocol.CongestionController, + }) + } + } + } + + return proxies, nil +} diff --git a/adapter/adapter_test.go b/adapter/adapter_test.go new file mode 100644 index 0000000..d45649b --- /dev/null +++ b/adapter/adapter_test.go @@ -0,0 +1,34 @@ +package adapter + +import ( + "testing" + "time" +) + +func TestAdapter_Client(t *testing.T) { + servers := getServers() + if len(servers) == 0 { + t.Errorf("[Test] No servers found") + return + } + a := NewAdapter(tpl, WithServers(servers), WithUserInfo(User{ + Password: "test-password", + ExpiredAt: time.Now().AddDate(1, 0, 0), + Download: 0, + Upload: 0, + Traffic: 1000, + SubscribeURL: "https://example.com/subscribe", + })) + client, err := a.Client() + if err != nil { + t.Errorf("[Test] Failed to get client: %v", err.Error()) + return + } + bytes, err := client.Build() + if err != nil { + t.Errorf("[Test] Failed to build client config: %v", err.Error()) + return + } + t.Logf("[Test] Client config built successfully: %s", string(bytes)) + +} diff --git a/adapter/client.go b/adapter/client.go new file mode 100644 index 0000000..e456898 --- /dev/null +++ b/adapter/client.go @@ -0,0 +1,146 @@ +package adapter + +import ( + "bytes" + "encoding/base64" + "reflect" + "text/template" + "time" + + "github.com/Masterminds/sprig/v3" +) + +type Proxy struct { + Sort int + Name string + Server string + Port uint16 + Type string + Tags []string + + // Security Options + Security string + SNI string // Server Name Indication for TLS + AllowInsecure bool // Allow insecure connections (skip certificate verification) + Fingerprint string // Client fingerprint for TLS connections + RealityServerAddr string // Reality server address + RealityServerPort int // Reality server port + RealityPrivateKey string // Reality private key for authentication + RealityPublicKey string // Reality public key for authentication + RealityShortId string // Reality short ID for authentication + // Transport Options + Transport string // Transport protocol (e.g., ws, http, grpc) + Host string // For WebSocket/HTTP/HTTPS + Path string // For HTTP/HTTPS + ServiceName string // For gRPC + // Shadowsocks Options + Method string + ServerKey string // For Shadowsocks 2022 + + // Vmess/Vless/Trojan Options + Flow string // Flow for Vmess/Vless/Trojan + // Hysteria2 Options + HopPorts string // Comma-separated list of hop ports + HopInterval int // Interval for hop ports in seconds + ObfsPassword string // Obfuscation password for Hysteria2 + UpMbps int // Upload speed in Mbps + DownMbps int // Download speed in Mbps + + // Tuic Options + DisableSNI bool // Disable SNI + ReduceRtt bool // Reduce RTT + UDPRelayMode string // UDP relay mode (e.g., "full", "partial") + CongestionController string // Congestion controller (e.g., "cubic", "bbr") + + // AnyTLS + PaddingScheme string + + // Mieru + Multiplex string + + // Obfs + //Obfs string // obfs, 'none', 'http', 'tls' + //ObfsHost string // obfs host + //ObfsPath string // obfs path + + // Vless + XhttpMode string // xhttp mode + XhttpExtra string // xhttp path + + // encryption + Encryption string // encryption,'none', 'mlkem768x25519plus' + EncryptionMode string // encryption mode,'native', 'xorpub', 'random' + EncryptionRtt string // encryption rtt,'0rtt', '1rtt' + EncryptionTicket string // encryption ticket + EncryptionServerPadding string // encryption server padding + EncryptionPrivateKey string // encryption private key + EncryptionClientPadding string // encryption client padding + EncryptionPassword string // encryption password + + Ratio float64 // Traffic ratio, default is 1 + CertMode string // Certificate mode, `none`|`http`|`dns`|`self` + CertDNSProvider string // DNS provider for certificate + CertDNSEnv string // Environment for DNS provider +} + +type User struct { + Password string + ExpiredAt time.Time + Download int64 + Upload int64 + Traffic int64 + SubscribeURL string +} + +type Client struct { + SiteName string // Name of the site + SubscribeName string // Name of the subscription + ClientTemplate string // Template for the entire client configuration + OutputFormat string // json, yaml, etc. + Proxies []Proxy // List of proxy configurations + UserInfo User // User information +} + +func (c *Client) Build() ([]byte, error) { + var buf bytes.Buffer + tmpl, err := template.New("client").Funcs(sprig.TxtFuncMap()).Parse(c.ClientTemplate) + if err != nil { + return nil, err + } + + proxies := make([]map[string]interface{}, len(c.Proxies)) + for i, p := range c.Proxies { + proxies[i] = StructToMap(p) + } + + err = tmpl.Execute(&buf, map[string]interface{}{ + "SiteName": c.SiteName, + "SubscribeName": c.SubscribeName, + "OutputFormat": c.OutputFormat, + "Proxies": proxies, + "UserInfo": c.UserInfo, + }) + if err != nil { + return nil, err + } + + result := buf.String() + if c.OutputFormat == "base64" { + encoded := base64.StdEncoding.EncodeToString([]byte(result)) + return []byte(encoded), nil + } + + return buf.Bytes(), nil +} + +func StructToMap(obj interface{}) map[string]interface{} { + m := make(map[string]interface{}) + v := reflect.ValueOf(obj) + t := reflect.TypeOf(obj) + + for i := 0; i < v.NumField(); i++ { + field := t.Field(i) + m[field.Name] = v.Field(i).Interface() + } + return m +} diff --git a/adapter/client_test.go b/adapter/client_test.go new file mode 100644 index 0000000..beb9145 --- /dev/null +++ b/adapter/client_test.go @@ -0,0 +1,153 @@ +package adapter + +import ( + "testing" + "time" +) + +var tpl = ` +{{- range $n := .Proxies }} + {{- $dn := urlquery (default "node" $n.Name) -}} + {{- $sni := default $n.Host $n.SNI -}} + + {{- if eq $n.Type "shadowsocks" -}} + {{- $userinfo := b64enc (print $n.Method ":" $.UserInfo.Password) -}} + {{- printf "ss://%s@%s:%v#%s" $userinfo $n.Host $n.Port $dn -}} + {{- "\n" -}} + {{- end -}} + + {{- if eq $n.Type "trojan" -}} + {{- $qs := "security=tls" -}} + {{- if $sni }}{{ $qs = printf "%s&sni=%s" $qs (urlquery $sni) }}{{ end -}} + {{- if $n.AllowInsecure }}{{ $qs = printf "%s&allowInsecure=%v" $qs $n.AllowInsecure }}{{ end -}} + {{- if $n.Fingerprint }}{{ $qs = printf "%s&fp=%s" $qs (urlquery $n.Fingerprint) }}{{ end -}} + {{- printf "trojan://%s@%s:%v?%s#%s" $.UserInfo.Password $n.Host $n.Port $qs $dn -}} + {{- "\n" -}} + {{- end -}} + + {{- if eq $n.Type "vless" -}} + {{- $qs := "encryption=none" -}} + {{- if $n.RealityPublicKey -}} + {{- $qs = printf "%s&security=reality" $qs -}} + {{- $qs = printf "%s&pbk=%s" $qs (urlquery $n.RealityPublicKey) -}} + {{- if $n.RealityShortId }}{{ $qs = printf "%s&sid=%s" $qs (urlquery $n.RealityShortId) }}{{ end -}} + {{- else -}} + {{- if or $n.SNI $n.Fingerprint $n.AllowInsecure }} + {{- $qs = printf "%s&security=tls" $qs -}} + {{- end -}} + {{- end -}} + {{- if $n.SNI }}{{ $qs = printf "%s&sni=%s" $qs (urlquery $n.SNI) }}{{ end -}} + {{- if $n.AllowInsecure }}{{ $qs = printf "%s&allowInsecure=%v" $qs $n.AllowInsecure }}{{ end -}} + {{- if $n.Fingerprint }}{{ $qs = printf "%s&fp=%s" $qs (urlquery $n.Fingerprint) }}{{ end -}} + {{- if $n.Network }}{{ $qs = printf "%s&type=%s" $qs $n.Network }}{{ end -}} + {{- if $n.Path }}{{ $qs = printf "%s&path=%s" $qs (urlquery $n.Path) }}{{ end -}} + {{- if $n.ServiceName }}{{ $qs = printf "%s&serviceName=%s" $qs (urlquery $n.ServiceName) }}{{ end -}} + {{- if $n.Flow }}{{ $qs = printf "%s&flow=%s" $qs (urlquery $n.Flow) }}{{ end -}} + {{- printf "vless://%s@%s:%v?%s#%s" $n.ServerKey $n.Host $n.Port $qs $dn -}} + {{- "\n" -}} + {{- end -}} + + {{- if eq $n.Type "vmess" -}} + {{- $obj := dict + "v" "2" + "ps" $n.Name + "add" $n.Host + "port" $n.Port + "id" $n.ServerKey + "aid" 0 + "net" (or $n.Network "tcp") + "type" "none" + "path" (or $n.Path "") + "host" $n.Host + -}} + {{- if or $n.SNI $n.Fingerprint $n.AllowInsecure }}{{ set $obj "tls" "tls" }}{{ end -}} + {{- if $n.SNI }}{{ set $obj "sni" $n.SNI }}{{ end -}} + {{- if $n.Fingerprint }}{{ set $obj "fp" $n.Fingerprint }}{{ end -}} + {{- printf "vmess://%s" (b64enc (toJson $obj)) -}} + {{- "\n" -}} + {{- end -}} + + {{- if or (eq $n.Type "hysteria2") (eq $n.Type "hy2") -}} + {{- $qs := "" -}} + {{- if $n.SNI }}{{ $qs = printf "sni=%s" (urlquery $n.SNI) }}{{ end -}} + {{- if $n.AllowInsecure }}{{ $qs = printf "%s&insecure=%v" $qs $n.AllowInsecure }}{{ end -}} + {{- if $n.ObfsPassword }}{{ $qs = printf "%s&obfs-password=%s" $qs (urlquery $n.ObfsPassword) }}{{ end -}} + {{- printf "hy2://%s@%s:%v%s#%s" + $.UserInfo.Password + $n.Host + $n.Port + (ternary (gt (len $qs) 0) (print "?" $qs) "") + $dn -}} + {{- "\n" -}} + {{- end -}} + + {{- if eq $n.Type "tuic" -}} + {{- $qs := "" -}} + {{- if $n.SNI }}{{ $qs = printf "sni=%s" (urlquery $n.SNI) }}{{ end -}} + {{- if $n.AllowInsecure }}{{ $qs = printf "%s&insecure=%v" $qs $n.AllowInsecure }}{{ end -}} + {{- printf "tuic://%s:%s@%s:%v%s#%s" + $n.ServerKey + $.UserInfo.Password + $n.Host + $n.Port + (ternary (gt (len $qs) 0) (print "?" $qs) "") + $dn -}} + {{- "\n" -}} + {{- end -}} + + {{- if eq $n.Type "anytls" -}} + {{- $qs := "" -}} + {{- if $n.SNI }}{{ $qs = printf "sni=%s" (urlquery $n.SNI) }}{{ end -}} + {{- printf "anytls://%s@%s:%v%s#%s" + $.UserInfo.Password + $n.Host + $n.Port + (ternary (gt (len $qs) 0) (print "?" $qs) "") + $dn -}} + {{- "\n" -}} + {{- end -}} + +{{- end }} +` + +func TestClient_Build(t *testing.T) { + client := &Client{ + SiteName: "TestSite", + SubscribeName: "TestSubscribe", + ClientTemplate: tpl, + Proxies: []Proxy{ + { + Name: "TestShadowSocks", + Type: "shadowsocks", + Host: "127.0.0.1", + Port: 1234, + Method: "aes-256-gcm", + }, + { + Name: "TestTrojan", + Type: "trojan", + Host: "example.com", + Port: 443, + AllowInsecure: true, + Security: "tls", + Transport: "tcp", + SNI: "v1-dy.ixigua.com", + }, + }, + UserInfo: User{ + Password: "testpassword", + ExpiredAt: time.Now().Add(24 * time.Hour), + Download: 1000000, + Upload: 500000, + Traffic: 1500000, + SubscribeURL: "https://example.com/subscribe", + }, + } + buf, err := client.Build() + if err != nil { + t.Fatalf("Failed to build client: %v", err) + } + + t.Logf("[测试] 输出: %s", buf) + +} diff --git a/adapter/utils.go b/adapter/utils.go new file mode 100644 index 0000000..b8e8da3 --- /dev/null +++ b/adapter/utils.go @@ -0,0 +1 @@ +package adapter diff --git a/adapter/utils_test.go b/adapter/utils_test.go new file mode 100644 index 0000000..7a4e32c --- /dev/null +++ b/adapter/utils_test.go @@ -0,0 +1,46 @@ +package adapter + +import ( + "testing" + + "github.com/perfect-panel/server/internal/model/server" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +func TestAdapterProxy(t *testing.T) { + + servers := getServers() + if len(servers) == 0 { + t.Fatal("no servers found") + } + for _, srv := range servers { + proxy, err := adapterProxy(*srv, "example.com", 0) + if err != nil { + t.Errorf("failed to adapt server %s: %v", srv.Name, err) + } + t.Logf("[测试] 适配服务器 %s 成功: %+v", srv.Name, proxy) + } + +} + +func getServers() []*server.Server { + db, err := connectMySQL("root:mylove520@tcp(localhost:3306)/perfectlink?charset=utf8mb4&parseTime=True&loc=Local") + if err != nil { + return nil + } + var servers []*server.Server + if err = db.Model(&server.Server{}).Find(&servers).Error; err != nil { + return nil + } + return servers +} +func connectMySQL(dsn string) (*gorm.DB, error) { + db, err := gorm.Open(mysql.New(mysql.Config{ + DSN: dsn, + }), &gorm.Config{}) + if err != nil { + return nil, err + } + return db, nil +} diff --git a/apis/admin/application.api b/apis/admin/application.api new file mode 100644 index 0000000..8ac5355 --- /dev/null +++ b/apis/admin/application.api @@ -0,0 +1,96 @@ +syntax = "v1" + +info ( + title: "Application API" + desc: "API for ppanel" + author: "Tension" + email: "tension@ppanel.com" + version: "0.0.1" +) + +import "../types.api" + +type ( + SubscribeApplication { + Id int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Icon string `json:"icon,omitempty"` + Scheme string `json:"scheme,omitempty"` + UserAgent string `json:"user_agent"` + IsDefault bool `json:"is_default"` + SubscribeTemplate string `json:"template"` + OutputFormat string `json:"output_format"` + DownloadLink DownloadLink `json:"download_link,omitempty"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` + } + GetSubscribeApplicationListResponse { + Total int64 `json:"total"` + List []SubscribeApplication `json:"list"` + } + CreateSubscribeApplicationRequest { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Icon string `json:"icon,omitempty"` + Scheme string `json:"scheme,omitempty"` + UserAgent string `json:"user_agent"` + IsDefault bool `json:"is_default"` + SubscribeTemplate string `json:"template"` + OutputFormat string `json:"output_format"` + DownloadLink DownloadLink `json:"download_link"` + } + UpdateSubscribeApplicationRequest { + Id int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Icon string `json:"icon,omitempty"` + Scheme string `json:"scheme,omitempty"` + UserAgent string `json:"user_agent"` + IsDefault bool `json:"is_default"` + SubscribeTemplate string `json:"template"` + OutputFormat string `json:"output_format"` + DownloadLink DownloadLink `json:"download_link,omitempty"` + } + DeleteSubscribeApplicationRequest { + Id int64 `json:"id"` + } + GetSubscribeApplicationListRequest { + Page int `form:"page"` + Size int `form:"size"` + } + PreviewSubscribeTemplateRequest { + Id int64 `form:"id"` + } + PreviewSubscribeTemplateResponse { + Template string `json:"template"` // 预览的模板内容 + } +) + +@server ( + prefix: v1/admin/application + group: admin/application + middleware: AuthMiddleware +) +service ppanel { + @doc "Create subscribe application" + @handler CreateSubscribeApplication + post / (CreateSubscribeApplicationRequest) returns (SubscribeApplication) + + @doc "Update subscribe application" + @handler UpdateSubscribeApplication + put /subscribe_application (UpdateSubscribeApplicationRequest) returns (SubscribeApplication) + + @doc "Get subscribe application list" + @handler GetSubscribeApplicationList + get /subscribe_application_list (GetSubscribeApplicationListRequest) returns (GetSubscribeApplicationListResponse) + + @doc "Delete subscribe application" + @handler DeleteSubscribeApplication + delete /subscribe_application (DeleteSubscribeApplicationRequest) + + @doc "Preview Template" + @handler PreviewSubscribeTemplate + get /preview (PreviewSubscribeTemplateRequest) returns (PreviewSubscribeTemplateResponse) +} + diff --git a/apis/admin/auth.api b/apis/admin/auth.api index 4161cb5..f1cd0d3 100644 --- a/apis/admin/auth.api +++ b/apis/admin/auth.api @@ -25,17 +25,14 @@ type ( GetAuthMethodListResponse { List []AuthMethodConfig `json:"list"` } - - TestSmsSendRequest { - AreaCode string `json:"area_code" validate:"required"` + AreaCode string `json:"area_code" validate:"required"` Telephone string `json:"telephone" validate:"required"` } // Test email smtp request TestEmailSendRequest { Email string `json:"email" validate:"required"` } - ) @server ( @@ -56,7 +53,6 @@ service ppanel { @handler UpdateAuthMethodConfig put /config (UpdateAuthMethodConfigRequest) returns (AuthMethodConfig) - @doc "Test sms send" @handler TestSmsSend post /test_sms_send (TestSmsSendRequest) diff --git a/apis/admin/console.api b/apis/admin/console.api index c20bb14..1a8ded7 100644 --- a/apis/admin/console.api +++ b/apis/admin/console.api @@ -21,7 +21,7 @@ type ( Download int64 `json:"download"` } ServerTotalDataResponse { - OnlineUserIPs int64 `json:"online_user_ips"` + OnlineUsers int64 `json:"online_users"` OnlineServers int64 `json:"online_servers"` OfflineServers int64 `json:"offline_servers"` TodayUpload int64 `json:"today_upload"` diff --git a/apis/admin/coupon.api b/apis/admin/coupon.api index 92516aa..7715f4d 100644 --- a/apis/admin/coupon.api +++ b/apis/admin/coupon.api @@ -21,8 +21,8 @@ type ( ExpireTime int64 `json:"expire_time" validate:"required"` UserLimit int64 `json:"user_limit,omitempty"` Subscribe []int64 `json:"subscribe,omitempty"` - UsedCount int64 `json:"used_count,omitempty"` - Enable *bool `json:"enable,omitempty"` + UsedCount int64 `json:"used_count,omitempty"` + Enable *bool `json:"enable,omitempty"` } UpdateCouponRequest { Id int64 `json:"id" validate:"required"` @@ -35,8 +35,8 @@ type ( ExpireTime int64 `json:"expire_time" validate:"required"` UserLimit int64 `json:"user_limit,omitempty"` Subscribe []int64 `json:"subscribe,omitempty"` - UsedCount int64 `json:"used_count,omitempty"` - Enable *bool `json:"enable,omitempty"` + UsedCount int64 `json:"used_count,omitempty"` + Enable *bool `json:"enable,omitempty"` } DeleteCouponRequest { Id int64 `json:"id" validate:"required"` diff --git a/apis/admin/device.api b/apis/admin/device.api index 1e5fb59..5301ab6 100644 --- a/apis/admin/device.api +++ b/apis/admin/device.api @@ -1,13 +1,10 @@ syntax = "v1" -info( - title: "Device API" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" +info ( + title: "Device API" + desc: "API for ppanel" + author: "Tension" + email: "tension@ppanel.com" + version: "0.0.1" ) -type ( - -) \ No newline at end of file diff --git a/apis/admin/log.api b/apis/admin/log.api index 12fc312..3b117c8 100644 --- a/apis/admin/log.api +++ b/apis/admin/log.api @@ -12,19 +12,184 @@ import "../types.api" type ( GetMessageLogListRequest { - Page int `form:"page"` - Size int `form:"size"` - Type string `form:"type"` - Platform string `form:"platform,omitempty"` - To string `form:"to,omitempty"` - Subject string `form:"subject,omitempty"` - Content string `form:"content,omitempty"` - Status int `form:"status,omitempty"` + Page int `form:"page"` + Size int `form:"size"` + Type uint8 `form:"type"` + Search string `form:"search,optional"` } GetMessageLogListResponse { Total int64 `json:"total"` List []MessageLog `json:"list"` } + FilterLogParams { + Page int `form:"page"` + Size int `form:"size"` + Date string `form:"date,optional"` + Search string `form:"search,optional"` + } + FilterEmailLogResponse { + Total int64 `json:"total"` + List []MessageLog `json:"list"` + } + FilterMobileLogResponse { + Total int64 `json:"total"` + List []MessageLog `json:"list"` + } + SubscribeLog { + UserId int64 `json:"user_id"` + Token string `json:"token"` + UserAgent string `json:"user_agent"` + ClientIP string `json:"client_ip"` + UserSubscribeId int64 `json:"user_subscribe_id"` + Timestamp int64 `json:"timestamp"` + } + FilterSubscribeLogRequest { + FilterLogParams + UserId int64 `form:"user_id,optional"` + UserSubscribeId int64 `form:"user_subscribe_id,optional"` + } + FilterSubscribeLogResponse { + Total int64 `json:"total"` + List []SubscribeLog `json:"list"` + } + LoginLog { + UserId int64 `json:"user_id"` + Method string `json:"method"` + LoginIP string `json:"login_ip"` + UserAgent string `json:"user_agent"` + Success bool `json:"success"` + Timestamp int64 `json:"timestamp"` + } + FilterLoginLogRequest { + FilterLogParams + UserId int64 `form:"user_id,optional"` + } + FilterLoginLogResponse { + Total int64 `json:"total"` + List []LoginLog `json:"list"` + } + RegisterLog { + UserId int64 `json:"user_id"` + AuthMethod string `json:"auth_method"` + Identifier string `json:"identifier"` + RegisterIP string `json:"register_ip"` + UserAgent string `json:"user_agent"` + Timestamp int64 `json:"timestamp"` + } + FilterRegisterLogRequest { + FilterLogParams + UserId int64 `form:"user_id,optional"` + } + FilterRegisterLogResponse { + Total int64 `json:"total"` + List []RegisterLog `json:"list"` + } + ResetSubscribeLog { + Type uint16 `json:"type"` + UserId int64 `json:"user_id"` + UserSubscribeId int64 `json:"user_subscribe_id"` + OrderNo string `json:"order_no,omitempty"` + Timestamp int64 `json:"timestamp"` + } + FilterResetSubscribeLogRequest { + FilterLogParams + UserSubscribeId int64 `form:"user_subscribe_id,optional"` + } + FilterResetSubscribeLogResponse { + Total int64 `json:"total"` + List []ResetSubscribeLog `json:"list"` + } + UserSubscribeTrafficLog { + SubscribeId int64 `json:"subscribe_id"` // Subscribe ID + UserId int64 `json:"user_id"` // User ID + Upload int64 `json:"upload"` // Upload traffic in bytes + Download int64 `json:"download"` // Download traffic in bytes + Total int64 `json:"total"` // Total traffic in bytes (Upload + Download) + Date string `json:"date"` // Date in YYYY-MM-DD format + Details bool `json:"details"` // Whether to show detailed traffic + } + FilterSubscribeTrafficRequest { + FilterLogParams + UserId int64 `form:"user_id,optional"` + UserSubscribeId int64 `form:"user_subscribe_id,optional"` + } + FilterSubscribeTrafficResponse { + Total int64 `json:"total"` + List []UserSubscribeTrafficLog `json:"list"` + } + ServerTrafficLog { + ServerId int64 `json:"server_id"` // Server ID + Upload int64 `json:"upload"` // Upload traffic in bytes + Download int64 `json:"download"` // Download traffic in bytes + Total int64 `json:"total"` // Total traffic in bytes (Upload + Download) + Date string `json:"date"` // Date in YYYY-MM-DD format + Details bool `json:"details"` // Whether to show detailed traffic + } + FilterServerTrafficLogRequest { + FilterLogParams + ServerId int64 `form:"server_id,optional"` + } + FilterServerTrafficLogResponse { + Total int64 `json:"total"` + List []ServerTrafficLog `json:"list"` + } + FilterBalanceLogRequest { + FilterLogParams + UserId int64 `form:"user_id,optional"` + } + FilterBalanceLogResponse { + Total int64 `json:"total"` + List []BalanceLog `json:"list"` + } + FilterCommissionLogRequest { + FilterLogParams + UserId int64 `form:"user_id,optional"` + } + FilterCommissionLogResponse { + Total int64 `json:"total"` + List []CommissionLog `json:"list"` + } + GiftLog { + Type uint16 `json:"type"` + userId int64 `json:"user_id"` + OrderNo string `json:"order_no"` + SubscribeId int64 `json:"subscribe_id"` + Amount int64 `json:"amount"` + Balance int64 `json:"balance"` + Remark string `json:"remark,omitempty"` + Timestamp int64 `json:"timestamp"` + } + FilterGiftLogRequest { + FilterLogParams + UserId int64 `form:"user_id,optional"` + } + FilterGiftLogResponse { + Total int64 `json:"total"` + List []GiftLog `json:"list"` + } + TrafficLogDetails { + Id int64 `json:"id"` + ServerId int64 `json:"server_id"` + UserId int64 `json:"user_id"` + SubscribeId int64 `json:"subscribe_id"` + Download int64 `json:"download"` + Upload int64 `json:"upload"` + Timestamp int64 `json:"timestamp"` + } + FilterTrafficLogDetailsRequest { + FilterLogParams + ServerId int64 `form:"server_id,optional"` + SubscribeId int64 `form:"subscribe_id,optional"` + UserId int64 `form:"user_id,optional"` + } + FilterTrafficLogDetailsResponse { + Total int64 `json:"total"` + List []TrafficLogDetails `json:"list"` + } + LogSetting { + AutoClear *bool `json:"auto_clear"` + ClearDays int64 `json:"clear_days"` + } ) @server ( @@ -36,5 +201,61 @@ service ppanel { @doc "Get message log list" @handler GetMessageLogList get /message/list (GetMessageLogListRequest) returns (GetMessageLogListResponse) + + @doc "Filter email log" + @handler FilterEmailLog + get /email/list (FilterLogParams) returns (FilterEmailLogResponse) + + @doc "Filter mobile log" + @handler FilterMobileLog + get /mobile/list (FilterLogParams) returns (FilterMobileLogResponse) + + @doc "Filter subscribe log" + @handler FilterSubscribeLog + get /subscribe/list (FilterSubscribeLogRequest) returns (FilterSubscribeLogResponse) + + @doc "Filter login log" + @handler FilterLoginLog + get /login/list (FilterLoginLogRequest) returns (FilterLoginLogResponse) + + @doc "Filter register log" + @handler FilterRegisterLog + get /register/list (FilterRegisterLogRequest) returns (FilterRegisterLogResponse) + + @doc "Filter reset subscribe log" + @handler FilterResetSubscribeLog + get /subscribe/reset/list (FilterResetSubscribeLogRequest) returns (FilterResetSubscribeLogResponse) + + @doc "Filter user subscribe traffic log" + @handler FilterUserSubscribeTrafficLog + get /subscribe/traffic/list (FilterSubscribeTrafficRequest) returns (FilterSubscribeTrafficResponse) + + @doc "Filter server traffic log" + @handler FilterServerTrafficLog + get /server/traffic/list (FilterServerTrafficLogRequest) returns (FilterServerTrafficLogResponse) + + @doc "Filter balance log" + @handler FilterBalanceLog + get /balance/list (FilterBalanceLogRequest) returns (FilterBalanceLogResponse) + + @doc "Filter commission log" + @handler FilterCommissionLog + get /commission/list (FilterCommissionLogRequest) returns (FilterCommissionLogResponse) + + @doc "Filter gift log" + @handler FilterGiftLog + get /gift/list (FilterGiftLogRequest) returns (FilterGiftLogResponse) + + @doc "Filter traffic log details" + @handler FilterTrafficLogDetails + get /traffic/details (FilterTrafficLogDetailsRequest) returns (FilterTrafficLogDetailsResponse) + + @doc "Get log setting" + @handler GetLogSetting + get /setting returns (LogSetting) + + @doc "Update log setting" + @handler UpdateLogSetting + post /setting (LogSetting) } diff --git a/apis/admin/marketing.api b/apis/admin/marketing.api new file mode 100644 index 0000000..8014c0d --- /dev/null +++ b/apis/admin/marketing.api @@ -0,0 +1,167 @@ +syntax = "v1" + +info ( + title: "Marketing API" + desc: "API for ppanel" + author: "Tension" + email: "tension@ppanel.com" + version: "0.0.1" +) + +type ( + CreateBatchSendEmailTaskRequest { + Subject string `json:"subject"` + Content string `json:"content"` + Scope int8 `json:"scope"` + RegisterStartTime int64 `json:"register_start_time,omitempty"` + RegisterEndTime int64 `json:"register_end_time,omitempty"` + Additional string `json:"additional,omitempty"` + Scheduled int64 `json:"scheduled,omitempty"` + Interval uint8 `json:"interval,omitempty"` + Limit uint64 `json:"limit,omitempty"` + } + BatchSendEmailTask { + Id int64 `json:"id"` + Subject string `json:"subject"` + Content string `json:"content"` + Recipients string `json:"recipients"` + Scope int8 `json:"scope"` + RegisterStartTime int64 `json:"register_start_time"` + RegisterEndTime int64 `json:"register_end_time"` + Additional string `json:"additional"` + Scheduled int64 `json:"scheduled"` + Interval uint8 `json:"interval"` + Limit uint64 `json:"limit"` + Status uint8 `json:"status"` + Errors string `json:"errors"` + Total uint64 `json:"total"` + Current uint64 `json:"current"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` + } + GetBatchSendEmailTaskListRequest { + Page int `form:"page"` + Size int `form:"size"` + Scope *int8 `form:"scope,omitempty"` + Status *uint8 `form:"status,omitempty"` + } + GetBatchSendEmailTaskListResponse { + Total int64 `json:"total"` + List []BatchSendEmailTask `json:"list"` + } + StopBatchSendEmailTaskRequest { + Id int64 `json:"id"` + } + GetPreSendEmailCountRequest { + Scope int8 `json:"scope"` + RegisterStartTime int64 `json:"register_start_time,omitempty"` + RegisterEndTime int64 `json:"register_end_time,omitempty"` + } + GetPreSendEmailCountResponse { + Count int64 `json:"count"` + } + GetBatchSendEmailTaskStatusRequest { + Id int64 `json:"id"` + } + GetBatchSendEmailTaskStatusResponse { + Status uint8 `json:"status"` + Current int64 `json:"current"` + Total int64 `json:"total"` + Errors string `json:"errors"` + } + CreateQuotaTaskRequest { + Subscribers []int64 `json:"subscribers"` + IsActive *bool `json:"is_active"` + StartTime int64 `json:"start_time"` + EndTime int64 `json:"end_time"` + ResetTraffic bool `json:"reset_traffic"` + Days uint64 `json:"days"` + GiftType uint8 `json:"gift_type"` + GiftValue uint64 `json:"gift_value"` + } + QuotaTask { + Id int64 `json:"id"` + Subscribers []int64 `json:"subscribers"` + IsActive *bool `json:"is_active"` + StartTime int64 `json:"start_time"` + EndTime int64 `json:"end_time"` + ResetTraffic bool `json:"reset_traffic"` + Days uint64 `json:"days"` + GiftType uint8 `json:"gift_type"` + GiftValue uint64 `json:"gift_value"` + Objects []int64 `json:"objects"` // UserSubscribe IDs + Status uint8 `json:"status"` + Total int64 `json:"total"` + Current int64 `json:"current"` + Errors string `json:"errors"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` + } + QueryQuotaTaskPreCountRequest { + Subscribers []int64 `json:"subscribers"` + IsActive *bool `json:"is_active"` + StartTime int64 `json:"start_time"` + EndTime int64 `json:"end_time"` + } + QueryQuotaTaskPreCountResponse { + Count int64 `json:"count"` + } + QueryQuotaTaskListRequest { + Page int `form:"page"` + Size int `form:"size"` + Status *uint8 `form:"status,omitempty"` + } + QueryQuotaTaskListResponse { + Total int64 `json:"total"` + List []QuotaTask `json:"list"` + } + QueryQuotaTaskStatusRequest { + Id int64 `json:"id"` + } + QueryQuotaTaskStatusResponse { + Status uint8 `json:"status"` + Current int64 `json:"current"` + Total int64 `json:"total"` + Errors string `json:"errors"` + } +) + +@server ( + prefix: v1/admin/marketing + group: admin/marketing + middleware: AuthMiddleware +) +service ppanel { + @doc "Create a batch send email task" + @handler CreateBatchSendEmailTask + post /email/batch/send (CreateBatchSendEmailTaskRequest) + + @doc "Get batch send email task list" + @handler GetBatchSendEmailTaskList + get /email/batch/list (GetBatchSendEmailTaskListRequest) returns (GetBatchSendEmailTaskListResponse) + + @doc "Stop a batch send email task" + @handler StopBatchSendEmailTask + post /email/batch/stop (StopBatchSendEmailTaskRequest) + + @doc "Get pre-send email count" + @handler GetPreSendEmailCount + post /email/batch/pre-send-count (GetPreSendEmailCountRequest) returns (GetPreSendEmailCountResponse) + + @doc "Get batch send email task status" + @handler GetBatchSendEmailTaskStatus + post /email/batch/status (GetBatchSendEmailTaskStatusRequest) returns (GetBatchSendEmailTaskStatusResponse) + + @doc "Create a quota task" + @handler CreateQuotaTask + post /quota/create (CreateQuotaTaskRequest) + + @doc "Query quota task pre-count" + @handler QueryQuotaTaskPreCount + post /quota/pre-count (QueryQuotaTaskPreCountRequest) returns (QueryQuotaTaskPreCountResponse) + + @doc "Query quota task list" + @handler QueryQuotaTaskList + get /quota/list (QueryQuotaTaskListRequest) returns (QueryQuotaTaskListResponse) +} + diff --git a/apis/admin/server.api b/apis/admin/server.api index d6181b0..2877427 100644 --- a/apis/admin/server.api +++ b/apis/admin/server.api @@ -11,104 +11,134 @@ info ( import "../types.api" type ( - GetNodeServerListRequest { - Page int `form:"page" validate:"required"` - Size int `form:"size" validate:"required"` - Tag string `form:"tag,omitempty"` - GroupId int64 `form:"group_id,omitempty"` - Search string `form:"search,omitempty"` + ServerOnlineIP { + IP string `json:"ip"` + Protocol string `json:"protocol"` } - GetNodeServerListResponse { + ServerOnlineUser { + IP []ServerOnlineIP `json:"ip"` + UserId int64 `json:"user_id"` + Subscribe string `json:"subscribe"` + SubscribeId int64 `json:"subscribe_id"` + Traffic int64 `json:"traffic"` + ExpiredAt int64 `json:"expired_at"` + } + ServerStatus { + Cpu float64 `json:"cpu"` + Mem float64 `json:"mem"` + Disk float64 `json:"disk"` + Protocol string `json:"protocol"` + Online []ServerOnlineUser `json:"online"` + Status string `json:"status"` + } + Server { + Id int64 `json:"id"` + Name string `json:"name"` + Country string `json:"country"` + City string `json:"city"` + Address string `json:"address"` + Sort int `json:"sort"` + Protocols []Protocol `json:"protocols"` + LastReportedAt int64 `json:"last_reported_at"` + Status ServerStatus `json:"status"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` + } + CreateServerRequest { + Name string `json:"name"` + Country string `json:"country,omitempty"` + City string `json:"city,omitempty"` + Address string `json:"address"` + Sort int `json:"sort,omitempty"` + Protocols []Protocol `json:"protocols"` + } + UpdateServerRequest { + Id int64 `json:"id"` + Name string `json:"name"` + Country string `json:"country,omitempty"` + City string `json:"city,omitempty"` + Address string `json:"address"` + Sort int `json:"sort,omitempty"` + Protocols []Protocol `json:"protocols"` + } + DeleteServerRequest { + Id int64 `json:"id"` + } + FilterServerListRequest { + Page int `form:"page"` + Size int `form:"size"` + Search string `form:"search,omitempty"` + } + FilterServerListResponse { Total int64 `json:"total"` List []Server `json:"list"` } - UpdateNodeRequest { - Id int64 `json:"id" validate:"required"` - Tags []string `json:"tags"` - Country string `json:"country"` - City string `json:"city"` - Name string `json:"name" validate:"required"` - ServerAddr string `json:"server_addr" validate:"required"` - RelayMode string `json:"relay_mode"` - RelayNode []NodeRelay `json:"relay_node"` - SpeedLimit int `json:"speed_limit"` - TrafficRatio float32 `json:"traffic_ratio"` - GroupId int64 `json:"group_id"` - Protocol string `json:"protocol" validate:"required"` - Config interface{} `json:"config" validate:"required"` - Enable *bool `json:"enable"` - Sort int64 `json:"sort"` + GetServerProtocolsRequest { + Id int64 `form:"id"` + } + GetServerProtocolsResponse { + Protocols []Protocol `json:"protocols"` + } + Node { + Id int64 `json:"id"` + Name string `json:"name"` + Tags []string `json:"tags"` + Port uint16 `json:"port"` + Address string `json:"address"` + ServerId int64 `json:"server_id"` + Protocol string `json:"protocol"` + Enabled *bool `json:"enabled"` + Sort int `json:"sort,omitempty"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` } CreateNodeRequest { - Name string `json:"name" validate:"required"` - Tags []string `json:"tags"` - Country string `json:"country"` - City string `json:"city"` - ServerAddr string `json:"server_addr" validate:"required"` - RelayMode string `json:"relay_mode"` - RelayNode []NodeRelay `json:"relay_node"` - SpeedLimit int `json:"speed_limit"` - TrafficRatio float32 `json:"traffic_ratio"` - GroupId int64 `json:"group_id"` - Protocol string `json:"protocol" validate:"required"` - Config interface{} `json:"config" validate:"required"` - Enable *bool `json:"enable"` - Sort int64 `json:"sort"` + Name string `json:"name"` + Tags []string `json:"tags,omitempty"` + Port uint16 `json:"port"` + Address string `json:"address"` + ServerId int64 `json:"server_id"` + Protocol string `json:"protocol"` + Enabled *bool `json:"enabled"` + } + UpdateNodeRequest { + Id int64 `json:"id"` + Name string `json:"name"` + Tags []string `json:"tags,omitempty"` + Port uint16 `json:"port"` + Address string `json:"address"` + ServerId int64 `json:"server_id"` + Protocol string `json:"protocol"` + Enabled *bool `json:"enabled"` + } + ToggleNodeStatusRequest { + Id int64 `json:"id"` + Enable *bool `json:"enable"` } DeleteNodeRequest { - Id int64 `json:"id" validate:"required"` + Id int64 `json:"id"` } - GetNodeGroupListResponse { - Total int64 `json:"total"` - List []ServerGroup `json:"list"` + FilterNodeListRequest { + Page int `form:"page"` + Size int `form:"size"` + Search string `form:"search,omitempty"` } - CreateNodeGroupRequest { - Name string `json:"name" validate:"required"` - Description string `json:"description"` + FilterNodeListResponse { + Total int64 `json:"total"` + List []Node `json:"list"` } - UpdateNodeGroupRequest { - Id int64 `json:"id" validate:"required"` - Name string `json:"name" validate:"required"` - Description string `json:"description"` + HasMigrateSeverNodeResponse { + HasMigrate bool `json:"has_migrate"` } - DeleteNodeGroupRequest { - Id int64 `json:"id" validate:"required"` + MigrateServerNodeResponse { + Succee uint64 `json:"succee"` + Fail uint64 `json:"fail"` + Message string `json:"message,omitempty"` } - BatchDeleteNodeRequest { - Ids []int64 `json:"ids" validate:"required"` - } - BatchDeleteNodeGroupRequest { - Ids []int64 `json:"ids" validate:"required"` - } - GetNodeDetailRequest { - Id int64 `form:"id" validate:"required"` - } - NodeSortRequest { + ResetSortRequest { Sort []SortItem `json:"sort"` } - CreateRuleGroupRequest { - Name string `json:"name" validate:"required"` - Icon string `json:"icon"` - Tags []string `json:"tags"` - Rules string `json:"rules"` - Enable bool `json:"enable"` - } - UpdateRuleGroupRequest { - Id int64 `json:"id" validate:"required"` - Icon string `json:"icon"` - Name string `json:"name" validate:"required"` - Tags []string `json:"tags"` - Rules string `json:"rules"` - Enable bool `json:"enable"` - } - DeleteRuleGroupRequest { - Id int64 `json:"id" validate:"required"` - } - GetRuleGroupResponse { - Total int64 `json:"total"` - List []ServerRuleGroup `json:"list"` - } - GetNodeTagListResponse { + QueryNodeTagResponse { Tags []string `json:"tags"` } ) @@ -119,72 +149,64 @@ type ( middleware: AuthMiddleware ) service ppanel { - @doc "Get node tag list" - @handler GetNodeTagList - get /tag/list returns (GetNodeTagListResponse) + @doc "Create Server" + @handler CreateServer + post /create (CreateServerRequest) - @doc "Get node list" - @handler GetNodeList - get /list (GetNodeServerListRequest) returns (GetNodeServerListResponse) + @doc "Update Server" + @handler UpdateServer + post /update (UpdateServerRequest) - @doc "Get node detail" - @handler GetNodeDetail - get /detail (GetNodeDetailRequest) returns (Server) + @doc "Delete Server" + @handler DeleteServer + post /delete (DeleteServerRequest) - @doc "Update node" - @handler UpdateNode - put / (UpdateNodeRequest) + @doc "Filter Server List" + @handler FilterServerList + get /list (FilterServerListRequest) returns (FilterServerListResponse) - @doc "Create node" + @doc "Get Server Protocols" + @handler GetServerProtocols + get /protocols (GetServerProtocolsRequest) returns (GetServerProtocolsResponse) + + @doc "Create Node" @handler CreateNode - post / (CreateNodeRequest) + post /node/create (CreateNodeRequest) - @doc "Delete node" + @doc "Update Node" + @handler UpdateNode + post /node/update (UpdateNodeRequest) + + @doc "Delete Node" @handler DeleteNode - delete / (DeleteNodeRequest) + post /node/delete (DeleteNodeRequest) - @doc "Batch delete node" - @handler BatchDeleteNode - delete /batch (BatchDeleteNodeRequest) + @doc "Filter Node List" + @handler FilterNodeList + get /node/list (FilterNodeListRequest) returns (FilterNodeListResponse) - @doc "Get node group list" - @handler GetNodeGroupList - get /group/list returns (GetNodeGroupListResponse) + @doc "Toggle Node Status" + @handler ToggleNodeStatus + post /node/status/toggle (ToggleNodeStatusRequest) - @doc "Create node group" - @handler CreateNodeGroup - post /group (CreateNodeGroupRequest) + @doc "Check if there is any server or node to migrate" + @handler HasMigrateSeverNode + get /migrate/has returns (HasMigrateSeverNodeResponse) - @doc "Update node group" - @handler UpdateNodeGroup - put /group (UpdateNodeGroupRequest) + @doc "Migrate server and node data to new database" + @handler MigrateServerNode + post /migrate/run returns (MigrateServerNodeResponse) - @doc "Delete node group" - @handler DeleteNodeGroup - delete /group (DeleteNodeGroupRequest) + @doc "Reset server sort" + @handler ResetSortWithServer + post /server/sort (ResetSortRequest) - @doc "Batch delete node group" - @handler BatchDeleteNodeGroup - delete /group/batch (BatchDeleteNodeGroupRequest) + @doc "Reset node sort" + @handler ResetSortWithNode + post /node/sort (ResetSortRequest) - @doc "Node sort " - @handler NodeSort - post /sort (NodeSortRequest) - - @doc "Create rule group" - @handler CreateRuleGroup - post /rule_group (CreateRuleGroupRequest) - - @doc "Update rule group" - @handler UpdateRuleGroup - put /rule_group (UpdateRuleGroupRequest) - - @doc "Delete rule group" - @handler DeleteRuleGroup - delete /rule_group (DeleteRuleGroupRequest) - - @doc "Get rule group list" - @handler GetRuleGroupList - get /rule_group_list returns (GetRuleGroupResponse) + @doc "Query all node tags" + @handler QueryNodeTag + get /node/tags returns (QueryNodeTagResponse) } diff --git a/apis/admin/subscribe.api b/apis/admin/subscribe.api index d95ab39..1d08b65 100644 --- a/apis/admin/subscribe.api +++ b/apis/admin/subscribe.api @@ -34,70 +34,67 @@ type ( Ids []int64 `json:"ids" validate:"required"` } CreateSubscribeRequest { - Name string `json:"name" validate:"required"` - Description string `json:"description"` - UnitPrice int64 `json:"unit_price"` - UnitTime string `json:"unit_time"` - Discount []SubscribeDiscount `json:"discount"` - Replacement int64 `json:"replacement"` - Inventory int64 `json:"inventory"` - Traffic int64 `json:"traffic"` - SpeedLimit int64 `json:"speed_limit"` - DeviceLimit int64 `json:"device_limit"` - Quota int64 `json:"quota"` - GroupId int64 `json:"group_id"` - ServerGroup []int64 `json:"server_group"` - Server []int64 `json:"server"` - Show *bool `json:"show"` - Sell *bool `json:"sell"` - DeductionRatio int64 `json:"deduction_ratio"` - AllowDeduction *bool `json:"allow_deduction"` - ResetCycle int64 `json:"reset_cycle"` - RenewalReset *bool `json:"renewal_reset"` + Name string `json:"name" validate:"required"` + Language string `json:"language"` + Description string `json:"description"` + UnitPrice int64 `json:"unit_price"` + UnitTime string `json:"unit_time"` + Discount []SubscribeDiscount `json:"discount"` + Replacement int64 `json:"replacement"` + Inventory int64 `json:"inventory"` + Traffic int64 `json:"traffic"` + SpeedLimit int64 `json:"speed_limit"` + DeviceLimit int64 `json:"device_limit"` + Quota int64 `json:"quota"` + Nodes []int64 `json:"nodes"` + NodeTags []string `json:"node_tags"` + Show *bool `json:"show"` + Sell *bool `json:"sell"` + DeductionRatio int64 `json:"deduction_ratio"` + AllowDeduction *bool `json:"allow_deduction"` + ResetCycle int64 `json:"reset_cycle"` + RenewalReset *bool `json:"renewal_reset"` } UpdateSubscribeRequest { - Id int64 `json:"id" validate:"required"` - Name string `json:"name" validate:"required"` - Description string `json:"description"` - UnitPrice int64 `json:"unit_price"` - UnitTime string `json:"unit_time"` - Discount []SubscribeDiscount `json:"discount"` - Replacement int64 `json:"replacement"` - Inventory int64 `json:"inventory"` - Traffic int64 `json:"traffic"` - SpeedLimit int64 `json:"speed_limit"` - DeviceLimit int64 `json:"device_limit"` - Quota int64 `json:"quota"` - GroupId int64 `json:"group_id"` - ServerGroup []int64 `json:"server_group"` - Server []int64 `json:"server"` - Show *bool `json:"show"` - Sell *bool `json:"sell"` - Sort int64 `json:"sort"` - DeductionRatio int64 `json:"deduction_ratio"` - AllowDeduction *bool `json:"allow_deduction"` - ResetCycle int64 `json:"reset_cycle"` - RenewalReset *bool `json:"renewal_reset"` + Id int64 `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + Language string `json:"language"` + Description string `json:"description"` + UnitPrice int64 `json:"unit_price"` + UnitTime string `json:"unit_time"` + Discount []SubscribeDiscount `json:"discount"` + Replacement int64 `json:"replacement"` + Inventory int64 `json:"inventory"` + Traffic int64 `json:"traffic"` + SpeedLimit int64 `json:"speed_limit"` + DeviceLimit int64 `json:"device_limit"` + Quota int64 `json:"quota"` + Nodes []int64 `json:"nodes"` + NodeTags []string `json:"node_tags"` + Show *bool `json:"show"` + Sell *bool `json:"sell"` + Sort int64 `json:"sort"` + DeductionRatio int64 `json:"deduction_ratio"` + AllowDeduction *bool `json:"allow_deduction"` + ResetCycle int64 `json:"reset_cycle"` + RenewalReset *bool `json:"renewal_reset"` } SubscribeSortRequest { Sort []SortItem `json:"sort"` } GetSubscribeListRequest { - Page int64 `form:"page" validate:"required"` - Size int64 `form:"size" validate:"required"` - GroupId int64 `form:"group_id,omitempty"` - Search string `form:"search,omitempty"` + Page int64 `form:"page" validate:"required"` + Size int64 `form:"size" validate:"required"` + Language string `form:"language,omitempty"` + Search string `form:"search,omitempty"` } - SubscribeItem { - Subscribe - - Sold int64 `json:"sold"` + Subscribe + Sold int64 `json:"sold"` } - GetSubscribeListResponse { List []SubscribeItem `json:"list"` - Total int64 `json:"total"` + Total int64 `json:"total"` } DeleteSubscribeRequest { Id int64 `json:"id" validate:"required"` diff --git a/apis/admin/system.api b/apis/admin/system.api index 09323c3..a300cda 100644 --- a/apis/admin/system.api +++ b/apis/admin/system.api @@ -11,50 +11,6 @@ info ( import "../types.api" type ( - // Update application request - UpdateApplicationRequest { - Id int64 `json:"id" validate:"required"` - Icon string `json:"icon"` - Name string `json:"name"` - Description string `json:"description"` - SubscribeType string `json:"subscribe_type"` - Platform ApplicationPlatform `json:"platform"` - } - // Create application request - CreateApplicationRequest { - Icon string `json:"icon"` - Name string `json:"name"` - Description string `json:"description"` - SubscribeType string `json:"subscribe_type"` - Platform ApplicationPlatform `json:"platform"` - } - // Update application request - UpdateApplicationVersionRequest { - Id int64 `json:"id" validate:"required"` - Url string `json:"url"` - Version string `json:"version" validate:"required"` - Description string `json:"description"` - Platform string `json:"platform" validate:"required,oneof=windows mac linux android ios harmony"` - IsDefault bool `json:"is_default"` - ApplicationId int64 `json:"application_id" validate:"required"` - } - // Create application request - CreateApplicationVersionRequest { - Url string `json:"url"` - Version string `json:"version" validate:"required"` - Description string `json:"description"` - Platform string `json:"platform" validate:"required,oneof=windows mac linux android ios harmony"` - IsDefault bool `json:"is_default"` - ApplicationId int64 `json:"application_id" validate:"required"` - } - // Delete application request - DeleteApplicationRequest { - Id int64 `json:"id" validate:"required"` - } - // Delete application request - DeleteApplicationVersionRequest { - Id int64 `json:"id" validate:"required"` - } GetNodeMultiplierResponse { Periods []TimePeriod `json:"periods"` } @@ -62,6 +18,10 @@ type ( SetNodeMultiplierRequest { Periods []TimePeriod `json:"periods"` } + PreViewNodeMultiplierResponse { + CurrentTime string `json:"current_time"` + Ratio float32 `json:"ratio"` + } ) @server ( @@ -86,46 +46,6 @@ service ppanel { @handler UpdateSubscribeConfig put /subscribe_config (SubscribeConfig) - @doc "Get subscribe type" - @handler GetSubscribeType - get /subscribe_type returns (SubscribeType) - - @doc "update application config" - @handler UpdateApplicationConfig - put /application_config (ApplicationConfig) - - @doc "get application config" - @handler GetApplicationConfig - get /application_config returns (ApplicationConfig) - - @doc "Get application" - @handler GetApplication - get /application returns (ApplicationResponse) - - @doc "Update application" - @handler UpdateApplication - put /application (UpdateApplicationRequest) - - @doc "Create application" - @handler CreateApplication - post /application (CreateApplicationRequest) - - @doc "Delete application" - @handler DeleteApplication - delete /application (DeleteApplicationRequest) - - @doc "Update application version" - @handler UpdateApplicationVersion - put /application_version (UpdateApplicationVersionRequest) - - @doc "Create application version" - @handler CreateApplicationVersion - post /application_version (CreateApplicationVersionRequest) - - @doc "Delete application" - @handler DeleteApplicationVersion - delete /application_version (DeleteApplicationVersionRequest) - @doc "Get register config" @handler GetRegisterConfig get /register_config returns (RegisterConfig) @@ -201,5 +121,9 @@ service ppanel { @doc "Update Verify Code Config" @handler UpdateVerifyCodeConfig put /verify_code_config (VerifyCodeConfig) + + @doc "PreView Node Multiplier" + @handler PreViewNodeMultiplier + get /node_multiplier/preview returns (PreViewNodeMultiplierResponse) } diff --git a/apis/admin/ticket.api b/apis/admin/ticket.api index 59ae897..c325bf0 100644 --- a/apis/admin/ticket.api +++ b/apis/admin/ticket.api @@ -12,14 +12,14 @@ import "../types.api" type ( UpdateTicketStatusRequest { - Id int64 `json:"id" validate:"required"` + Id int64 `json:"id" validate:"required"` Status *uint8 `json:"status" validate:"required"` } GetTicketListRequest { Page int64 `form:"page"` Size int64 `form:"size"` UserId int64 `form:"user_id,omitempty"` - Status *uint8 `form:"status,omitempty"` + Status *uint8 `form:"status,omitempty"` Search string `form:"search,omitempty"` } GetTicketListResponse { diff --git a/apis/admin/tool.api b/apis/admin/tool.api index f54ee61..da1916b 100644 --- a/apis/admin/tool.api +++ b/apis/admin/tool.api @@ -1,33 +1,40 @@ syntax = "v1" -info( - title: "Tools Api" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" +info ( + title: "Tools Api" + desc: "API for ppanel" + author: "Tension" + email: "tension@ppanel.com" + version: "0.0.1" ) import "../types.api" type ( - LogResponse { - List interface{} `json:"list"` - } + LogResponse { + List interface{} `json:"list"` + } + VersionResponse { + Version string `json:"version"` + } ) @server ( - prefix: v1/admin/tool - group: admin/tool - middleware: AuthMiddleware + prefix: v1/admin/tool + group: admin/tool + middleware: AuthMiddleware ) - service ppanel { - @doc "Get System Log" - @handler GetSystemLog - get /log returns (LogResponse) + @doc "Get System Log" + @handler GetSystemLog + get /log returns (LogResponse) - @doc "Restart System" - @handler RestartSystem - get /restart + @doc "Restart System" + @handler RestartSystem + get /restart + + @doc "Get Version" + @handler GetVersion + get /version returns (VersionResponse) } + diff --git a/apis/admin/user.api b/apis/admin/user.api index 92149fd..dc0c5e8 100644 --- a/apis/admin/user.api +++ b/apis/admin/user.api @@ -32,17 +32,19 @@ type ( Id int64 `form:"id" validate:"required"` } UpdateUserBasiceInfoRequest { - UserId int64 `json:"user_id" validate:"required"` - Password string `json:"password"` - Avatar string `json:"avatar"` - Balance int64 `json:"balance"` - Commission int64 `json:"commission"` - GiftAmount int64 `json:"gift_amount"` - Telegram int64 `json:"telegram"` - ReferCode string `json:"refer_code"` - RefererId int64 `json:"referer_id"` - Enable bool `json:"enable"` - IsAdmin bool `json:"is_admin"` + UserId int64 `json:"user_id" validate:"required"` + Password string `json:"password"` + Avatar string `json:"avatar"` + Balance int64 `json:"balance"` + Commission int64 `json:"commission"` + ReferralPercentage uint8 `json:"referral_percentage"` + OnlyFirstPurchase bool `json:"only_first_purchase"` + GiftAmount int64 `json:"gift_amount"` + Telegram int64 `json:"telegram"` + ReferCode string `json:"refer_code"` + RefererId int64 `json:"referer_id"` + Enable bool `json:"enable"` + IsAdmin bool `json:"is_admin"` } UpdateUserNotifySettingRequest { UserId int64 `json:"user_id" validate:"required"` @@ -52,18 +54,20 @@ type ( EnableTradeNotify bool `json:"enable_trade_notify"` } CreateUserRequest { - Email string `json:"email"` - Telephone string `json:"telephone"` - TelephoneAreaCode string `json:"telephone_area_code"` - Password string `json:"password"` - ProductId int64 `json:"product_id"` - Duration int64 `json:"duration"` - RefererUser string `json:"referer_user"` - ReferCode string `json:"refer_code"` - Balance int64 `json:"balance"` - Commission int64 `json:"commission"` - GiftAmount int64 `json:"gift_amount"` - IsAdmin bool `json:"is_admin"` + Email string `json:"email"` + Telephone string `json:"telephone"` + TelephoneAreaCode string `json:"telephone_area_code"` + Password string `json:"password"` + ProductId int64 `json:"product_id"` + Duration int64 `json:"duration"` + ReferralPercentage uint8 `json:"referral_percentage"` + OnlyFirstPurchase bool `json:"only_first_purchase"` + RefererUser string `json:"referer_user"` + ReferCode string `json:"refer_code"` + Balance int64 `json:"balance"` + Commission int64 `json:"commission"` + GiftAmount int64 `json:"gift_amount"` + IsAdmin bool `json:"is_admin"` } UserSubscribeDetail { Id int64 `json:"id"` @@ -164,6 +168,15 @@ type ( List []UserLoginLog `json:"list"` Total int64 `json:"total"` } + GetUserSubscribeResetTrafficLogsRequest { + Page int `form:"page"` + Size int `form:"size"` + UserSubscribeId int64 `form:"user_subscribe_id"` + } + GetUserSubscribeResetTrafficLogsResponse { + List []ResetSubscribeTrafficLog `json:"list"` + Total int64 `json:"total"` + } DeleteUserSubscribeRequest { UserSubscribeId int64 `json:"user_subscribe_id"` } @@ -251,6 +264,10 @@ service ppanel { @handler GetUserSubscribeLogs get /subscribe/logs (GetUserSubscribeLogsRequest) returns (GetUserSubscribeLogsResponse) + @doc "Get user subcribe reset traffic logs" + @handler GetUserSubscribeResetTrafficLogs + get /subscribe/reset/logs (GetUserSubscribeResetTrafficLogsRequest) returns (GetUserSubscribeResetTrafficLogsResponse) + @doc "Get user subcribe traffic logs" @handler GetUserSubscribeTrafficLogs get /subscribe/traffic_logs (GetUserSubscribeTrafficLogsRequest) returns (GetUserSubscribeTrafficLogsResponse) diff --git a/apis/app/announcement.api b/apis/app/announcement.api deleted file mode 100644 index 2decd09..0000000 --- a/apis/app/announcement.api +++ /dev/null @@ -1,24 +0,0 @@ -syntax = "v1" - -info ( - title: "Announcement API" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" -) - -import "../types.api" - - -@server ( - prefix: v1/app/announcement - group: app/announcement - middleware: AppMiddleware,AuthMiddleware -) -service ppanel { - @doc "Query announcement" - @handler QueryAnnouncement - get /list (QueryAnnouncementRequest) returns (QueryAnnouncementResponse) -} - diff --git a/apis/app/auth.api b/apis/app/auth.api deleted file mode 100644 index 015eb64..0000000 --- a/apis/app/auth.api +++ /dev/null @@ -1,105 +0,0 @@ -syntax = "v1" - -info( - title: "App Auth Api" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" -) - -import ( - "../types.api" -) - -type ( - AppAuthCheckRequest { - Method string `json:"method" validate:"required" validate:"required,oneof=device email mobile"` - Account string `json:"account"` - Identifier string `json:"identifier" validate:"required"` - UserAgent string `json:"user_agent" validate:"required,oneof=windows mac linux android ios harmony"` - AreaCode string `json:"area_code"` - } - AppAuthCheckResponse { - Status bool - } - AppAuthRequest { - Method string `json:"method" validate:"required" validate:"required,oneof=device email mobile"` - Account string `json:"account"` - Password string `json:"password"` - Identifier string `json:"identifier" validate:"required"` - UserAgent string `json:"user_agent" validate:"required,oneof=windows mac linux android ios harmony"` - Code string `json:"code"` - Invite string `json:"invite"` - AreaCode string `json:"area_code"` - CfToken string `json:"cf_token,optional"` - } - AppAuthRespone { - Token string `json:"token"` - } - AppSendCodeRequest { - Method string `json:"method" validate:"required" validate:"required,oneof=email mobile"` - Account string `json:"account"` - AreaCode string `json:"area_code"` - CfToken string `json:"cf_token,optional"` - } - AppSendCodeRespone { - Status bool `json:"status"` - Code string `json:"code,omitempty"` - } - AppConfigRequest { - UserAgent string `json:"user_agent" validate:"required,oneof=windows mac linux android ios harmony"` - } - AppConfigResponse { - EncryptionKey string `json:"encryption_key"` - EncryptionMethod string `json:"encryption_method"` - Domains []string `json:"domains"` - StartupPicture string `json:"startup_picture"` - StartupPictureSkipTime int64 `json:"startup_picture_skip_time"` - Application AppInfo `json:"applications"` - OfficialEmail string `json:"official_email"` - OfficialWebsite string `json:"official_website"` - OfficialTelegram string `json:"official_telegram"` - OfficialTelephone string `json:"official_telephone"` - InvitationLink string `json:"invitation_link"` - KrWebsiteId string `json:"kr_website_id"` - } - AppInfo { - Id int64 `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Url string `json:"url"` - Version string `json:"version"` - VersionReview string `json:"version_review"` - VersionDescription string `json:"version_description"` - IsDefault bool `json:"is_default"` - } -) - -@server( - prefix: v1/app/auth - group: app/auth - middleware: AppMiddleware -) -service ppanel { - @doc "Check Account" - @handler Check - post /check (AppAuthCheckRequest) returns (AppAuthCheckResponse) - - @doc "Login" - @handler Login - post /login (AppAuthRequest) returns (AppAuthRespone) - - @doc "Register" - @handler Register - post /register (AppAuthRequest) returns (AppAuthRespone) - - @doc "Reset Password" - @handler ResetPassword - post /reset_password (AppAuthRequest) returns (AppAuthRespone) - - @doc "GetAppConfig" - @handler GetAppConfig - post /config (AppConfigRequest) returns (AppConfigResponse) -} - diff --git a/apis/app/document.api b/apis/app/document.api deleted file mode 100644 index 6cc4c71..0000000 --- a/apis/app/document.api +++ /dev/null @@ -1,27 +0,0 @@ -syntax = "v1" - -info( - title: "Document API" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" -) - -import "../types.api" - -@server ( - prefix: v1/app/document - group: app/document - middleware: AppMiddleware,AuthMiddleware -) - -service ppanel { - @doc "Get document list" - @handler QueryDocumentList - get /list returns (QueryDocumentListResponse) - - @doc "Get document detail" - @handler QueryDocumentDetail - get /detail (QueryDocumentDetailRequest) returns (Document) -} \ No newline at end of file diff --git a/apis/app/node.api b/apis/app/node.api deleted file mode 100644 index b2434b2..0000000 --- a/apis/app/node.api +++ /dev/null @@ -1,49 +0,0 @@ -syntax = "v1" - - -info( - title: "App Node Api" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" -) - -import "../types.api" - -type( - - - AppRuleGroupListResponse { - Total int64 `json:"total"` - List []ServerRuleGroup `json:"list"` - } - - AppUserSubscbribeNodeRequest { - Id int64 `form:"id" validate:"required"` - } - - AppUserSubscbribeNodeResponse{ - List []AppUserSubscbribeNode `json:"list"` - } -) - -@server ( - prefix: v1/app/node - group: app/node - middleware: AppMiddleware,AuthMiddleware -) - -service ppanel { - - - - @doc "Get Node list" - @handler GetNodeList - get /list (AppUserSubscbribeNodeRequest) returns(AppUserSubscbribeNodeResponse) - - @doc "Get rule group list" - @handler GetRuleGroupList - get /rule_group_list returns (AppRuleGroupListResponse) - -} \ No newline at end of file diff --git a/apis/app/order.api b/apis/app/order.api deleted file mode 100644 index 959ff40..0000000 --- a/apis/app/order.api +++ /dev/null @@ -1,58 +0,0 @@ -syntax = "v1" - -info ( - title: "Order API" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" -) - -import ( - "../types.api" -) - - -@server ( - prefix: v1/app/order - group: app/order - middleware: AppMiddleware,AuthMiddleware -) -service ppanel { - @doc "Pre create order" - @handler PreCreateOrder - post /pre (PurchaseOrderRequest) returns (PreOrderResponse) - - @doc "purchase Subscription" - @handler Purchase - post /purchase (PurchaseOrderRequest) returns (PurchaseOrderResponse) - - @doc "Renewal Subscription" - @handler Renewal - post /renewal (RenewalOrderRequest) returns (RenewalOrderResponse) - - @doc "Reset traffic" - @handler ResetTraffic - post /reset (ResetTrafficOrderRequest) returns (ResetTrafficOrderResponse) - - @doc "Recharge" - @handler Recharge - post /recharge (RechargeOrderRequest) returns (RechargeOrderResponse) - - @doc "Checkout order" - @handler CheckoutOrder - post /checkout (CheckoutOrderRequest) returns (CheckoutOrderResponse) - - @doc "Close order" - @handler CloseOrder - post /close (CloseOrderRequest) - - @doc "Get order" - @handler QueryOrderDetail - get /detail (QueryOrderDetailRequest) returns (OrderDetail) - - @doc "Get order list" - @handler QueryOrderList - get /list (QueryOrderListRequest) returns (QueryOrderListResponse) -} - diff --git a/apis/app/payment.api b/apis/app/payment.api deleted file mode 100644 index 9769a47..0000000 --- a/apis/app/payment.api +++ /dev/null @@ -1,23 +0,0 @@ -syntax = "v1" - -info ( - title: "payment API" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" -) - -import "../types.api" - -@server ( - prefix: v1/app/payment - group: app/payment - middleware: AppMiddleware,AuthMiddleware -) -service ppanel { - @doc "Get available payment methods" - @handler GetAvailablePaymentMethods - get /methods returns (GetAvailablePaymentMethodsResponse) -} - diff --git a/apis/app/subscribe.api b/apis/app/subscribe.api deleted file mode 100644 index 0a4a05a..0000000 --- a/apis/app/subscribe.api +++ /dev/null @@ -1,75 +0,0 @@ -syntax = "v1" - -info( - title: "Subscribe API" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" -) - -import "../types.api" - - -type ( - QueryUserSubscribeResp { - Data []UserSubscribeData `json:"data"` - } - - UserSubscribeData { - SubscribeId int64 `json:"subscribe_id"` - UserSubscribeId int64 `json:"user_subscribe_id"` - } - - - UserSubscribeResetPeriodRequest { - UserSubscribeId int64 `json:"user_subscribe_id"` - } - - UserSubscribeResetPeriodResponse { - Status bool `json:"status"` - } - - AppUserSubscribeRequest { - ContainsNodes *bool `form:"contains_nodes"` - } - - AppUserSubscbribeResponse { - List []AppUserSubcbribe `json:"list"` - } -) - -@server( - prefix: v1/app/subscribe - group: app/subscribe - middleware: AppMiddleware,AuthMiddleware -) - - -service ppanel { - @doc "Get subscribe list" - @handler QuerySubscribeList - get /list returns (QuerySubscribeListResponse) - - @doc "Get subscribe group list" - @handler QuerySubscribeGroupList - get /group/list returns (QuerySubscribeGroupListResponse) - - @doc "Get application config" - @handler QueryApplicationConfig - get /application/config returns (ApplicationResponse) - - @doc "Get Already subscribed to package" - @handler QueryUserAlreadySubscribe - get /user/already_subscribe returns (QueryUserSubscribeResp) - - - @doc "Get Available subscriptions for users" - @handler QueryUserAvailableUserSubscribe - get /user/available_subscribe (AppUserSubscribeRequest) returns (AppUserSubscbribeResponse) - - @doc "Reset user subscription period" - @handler ResetUserSubscribePeriod - post /reset/period (UserSubscribeResetPeriodRequest) returns (UserSubscribeResetPeriodResponse) -} - diff --git a/apis/app/user.api b/apis/app/user.api deleted file mode 100644 index 67fb380..0000000 --- a/apis/app/user.api +++ /dev/null @@ -1,90 +0,0 @@ -syntax = "v1" - -info ( - title: "App User Api" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" -) - -import ( - "../types.api" -) - -type ( - UserInfoResponse { - Id int64 `json:"id"` - Balance int64 `json:"balance"` - Email string `json:"email"` - RefererId int64 `json:"referer_id"` - ReferCode string `json:"refer_code"` - Avatar string `json:"avatar"` - AreaCode string `json:"area_code"` - Telephone string `json:"telephone"` - Devices []UserDevice `json:"devices"` - AuthMethods []UserAuthMethod `json:"auth_methods"` - } - UpdatePasswordRequeset { - Password string `json:"password"` - NewPassword string `json:"new_password"` - } - DeleteAccountRequest { - Method string `json:"method" validate:"required" validate:"required,oneof=email telephone device"` - Code string `json:"code"` - } - - GetUserOnlineTimeStatisticsResponse{ - WeeklyStats []WeeklyStat`json:"weekly_stats"` - ConnectionRecords ConnectionRecords`json:"connection_records"` - } - - WeeklyStat{ - Day int `json:"day"` - DayName string `json:"day_name"` - Hours float64 `json:"hours"` - } - ConnectionRecords{ - CurrentContinuousDays int64 `json:"current_continuous_days"` - HistoryContinuousDays int64 `json:"history_continuous_days"` - LongestSingleConnection int64 `json:"longest_single_connection"` - } -) - -@server ( - prefix: v1/app/user - group: app/user - middleware: AppMiddleware,AuthMiddleware -) -service ppanel { - @doc "query user info" - @handler QueryUserInfo - get /info returns (UserInfoResponse) - - @doc "Update Password " - @handler UpdatePassword - put /password (UpdatePasswordRequeset) - - @doc "Delete Account" - @handler DeleteAccount - delete /account (DeleteAccountRequest) - - @doc "Get user subcribe traffic logs" - @handler GetUserSubscribeTrafficLogs - get /subscribe/traffic_logs (GetUserSubscribeTrafficLogsRequest) returns (GetUserSubscribeTrafficLogsResponse) - - @doc "Get user online time total" - @handler GetUserOnlineTimeStatistics - get /online_time/statistics returns (GetUserOnlineTimeStatisticsResponse) - - @doc "Query User Affiliate List" - @handler QueryUserAffiliateList - get /affiliate/list (QueryUserAffiliateListRequest) returns (QueryUserAffiliateListResponse) - - @doc "Query User Affiliate Count" - @handler QueryUserAffiliate - get /affiliate/count returns (QueryUserAffiliateCountResponse) - - -} - diff --git a/apis/app/ws.api b/apis/app/ws.api deleted file mode 100644 index a3a26be..0000000 --- a/apis/app/ws.api +++ /dev/null @@ -1,23 +0,0 @@ -syntax = "v1" - -info( - title: "App Heartbeat Api" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" -) - - -@server( - prefix: v1/app/ws - group: app/ws - middleware: AuthMiddleware -) - - -service ppanel { - @doc "App heartbeat" - @handler AppWs - get /:userid/:identifier -} \ No newline at end of file diff --git a/apis/auth/auth.api b/apis/auth/auth.api index 22dbe10..154c878 100644 --- a/apis/auth/auth.api +++ b/apis/auth/auth.api @@ -11,11 +11,13 @@ info ( type ( // User login request UserLoginRequest { - Email string `json:"email" validate:"required"` - Password string `json:"password" validate:"required"` - IP string `header:"X-Original-Forwarded-For"` - UserAgent string `header:"User-Agent"` - CfToken string `json:"cf_token,optional"` + Identifier string `json:"identifier"` + Email string `json:"email" validate:"required"` + Password string `json:"password" validate:"required"` + IP string `header:"X-Original-Forwarded-For"` + UserAgent string `header:"User-Agent"` + LoginType string `header:"Login-Type"` + CfToken string `json:"cf_token,optional"` } // Check user is exist request CheckUserRequest { @@ -23,50 +25,55 @@ type ( } // User login response CheckUserResponse { - exist bool `json:"exist"` + Exist bool `json:"exist"` } // User login response UserRegisterRequest { - Email string `json:"email" validate:"required"` - Password string `json:"password" validate:"required"` - Invite string `json:"invite,optional"` - Code string `json:"code,optional"` - IP string `header:"X-Original-Forwarded-For"` - UserAgent string `header:"User-Agent"` - CfToken string `json:"cf_token,optional"` + Identifier string `json:"identifier"` + Email string `json:"email" validate:"required"` + Password string `json:"password" validate:"required"` + Invite string `json:"invite,optional"` + Code string `json:"code,optional"` + IP string `header:"X-Original-Forwarded-For"` + UserAgent string `header:"User-Agent"` + LoginType string `header:"Login-Type"` + CfToken string `json:"cf_token,optional"` } // User login response ResetPasswordRequest { - Email string `json:"email" validate:"required"` - Password string `json:"password" validate:"required"` - Code string `json:"code,optional"` - IP string `header:"X-Original-Forwarded-For"` + Identifier string `json:"identifier"` + Email string `json:"email" validate:"required"` + Password string `json:"password" validate:"required"` + Code string `json:"code,optional"` + IP string `header:"X-Original-Forwarded-For"` UserAgent string `header:"User-Agent"` - CfToken string `json:"cf_token,optional"` + LoginType string `header:"Login-Type"` + CfToken string `json:"cf_token,optional"` } LoginResponse { Token string `json:"token"` } OAthLoginRequest { - Method string `json:"method" validate:"required"` // google, facebook, apple, telegram, github etc. + Method string `json:"method" validate:"required"` // google, facebook, apple, telegram, github etc. Redirect string `json:"redirect"` } OAuthLoginResponse { Redirect string `json:"redirect"` } - OAuthLoginGetTokenRequest { - Method string `json:"method" validate:"required"` // google, facebook, apple, telegram, github etc. + Method string `json:"method" validate:"required"` // google, facebook, apple, telegram, github etc. Callback interface{} `json:"callback" validate:"required"` } - // login request TelephoneLoginRequest { + Identifier string `json:"identifier"` Telephone string `json:"telephone" validate:"required"` TelephoneCode string `json:"telephone_code"` TelephoneAreaCode string `json:"telephone_area_code" validate:"required"` Password string `json:"password"` IP string `header:"X-Original-Forwarded-For"` + UserAgent string `header:"User-Agent"` + LoginType string `header:"Login-Type"` CfToken string `json:"cf_token,optional"` } // Check user is exist request @@ -76,25 +83,31 @@ type ( } // User login response TelephoneCheckUserResponse { - exist bool `json:"exist"` + Exist bool `json:"exist"` } // User login response TelephoneRegisterRequest { + Identifier string `json:"identifier"` Telephone string `json:"telephone" validate:"required"` TelephoneAreaCode string `json:"telephone_area_code" validate:"required"` Password string `json:"password" validate:"required"` Invite string `json:"invite,optional"` Code string `json:"code,optional"` IP string `header:"X-Original-Forwarded-For"` + UserAgent string `header:"User-Agent"` + LoginType string `header:"Login-Type,optional"` CfToken string `json:"cf_token,optional"` } // User login response TelephoneResetPasswordRequest { + Identifier string `json:"identifier"` Telephone string `json:"telephone" validate:"required"` TelephoneAreaCode string `json:"telephone_area_code" validate:"required"` Password string `json:"password" validate:"required"` Code string `json:"code,optional"` IP string `header:"X-Original-Forwarded-For"` + UserAgent string `header:"User-Agent"` + LoginType string `header:"Login-Type,optional"` CfToken string `json:"cf_token,optional"` } AppleLoginCallbackRequest { @@ -103,14 +116,21 @@ type ( State string `form:"state"` } GoogleLoginCallbackRequest { - Code string `form:"code"` + Code string `form:"code"` 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 ( prefix: v1/auth group: auth + middleware: DeviceMiddleware ) service ppanel { @doc "User login" @@ -144,6 +164,10 @@ service ppanel { @doc "Reset password" @handler TelephoneResetPassword post /reset/telephone (TelephoneResetPasswordRequest) returns (LoginResponse) + + @doc "Device Login" + @handler DeviceLogin + post /login/device (DeviceLoginRequest) returns (LoginResponse) } @server ( diff --git a/apis/common.api b/apis/common.api index 545c3c9..d246099 100644 --- a/apis/common.api +++ b/apis/common.api @@ -35,10 +35,6 @@ type ( GetTosResponse { TosContent string `json:"tos_content"` } - GetAppcationResponse { - Config ApplicationConfig `json:"config"` - Applications []ApplicationResponseInfo `json:"applications"` - } // GetCodeRequest Get code request SendCodeRequest { Email string `json:"email" validate:"required"` @@ -70,30 +66,39 @@ type ( List []Ads `json:"list"` } CheckVerificationCodeRequest { - Method string `json:"method" validate:"required,oneof=email mobile"` + Method string `json:"method" validate:"required,oneof=email mobile"` Account string `json:"account" validate:"required"` - Code string `json:"code" validate:"required"` - Type uint8 `json:"type" validate:"required"` + Code string `json:"code" validate:"required"` + Type uint8 `json:"type" validate:"required"` } - - CheckVerificationCodeRespone{ - Status bool `json:"status"` + CheckVerificationCodeRespone { + Status bool `json:"status"` + } + SubscribeClient { + Id int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Icon string `json:"icon,omitempty"` + Scheme string `json:"scheme,omitempty"` + IsDefault bool `json:"is_default"` + DownloadLink DownloadLink `json:"download_link,omitempty"` + } + GetSubscribeClientResponse { + Total int64 `json:"total"` + List []SubscribeClient `json:"list"` } ) @server ( prefix: v1/common group: common + middleware: DeviceMiddleware ) service ppanel { @doc "Get global config" @handler GetGlobalConfig get /site/config returns (GetGlobalConfigResponse) - @doc "Get Tos Content" - @handler GetApplication - get /application returns (GetAppcationResponse) - @doc "Get Tos Content" @handler GetTos get /site/tos returns (GetTosResponse) @@ -121,5 +126,9 @@ service ppanel { @doc "Check verification code" @handler CheckVerificationCode post /check_verification_code (CheckVerificationCodeRequest) returns (CheckVerificationCodeRespone) + + @doc "Get Client" + @handler GetClient + get /client returns (GetSubscribeClientResponse) } diff --git a/apis/node/node.api b/apis/node/node.api index 8195854..8dee713 100644 --- a/apis/node/node.api +++ b/apis/node/node.api @@ -11,6 +11,10 @@ info ( import "../types.api" type ( + OnlineUser { + SID int64 `json:"uid"` + IP string `json:"ip"` + } ShadowsocksProtocol { Port int `json:"port"` Method string `json:"method"` @@ -44,9 +48,9 @@ type ( ServerCommon } GetServerConfigResponse { - Basic ServerBasic `json:"basic"` - Protocol string `json:"protocol"` - Config interface{} `json:"config"` + Basic ServerBasic `json:"basic"` + Protocol string `json:"protocol"` + Config interface{} `json:"config"` } ServerBasic { PushInterval int64 `json:"push_interval"` @@ -60,8 +64,8 @@ type ( ServerUser { Id int64 `json:"id"` UUID string `json:"uuid"` - SpeedLimit int64 `json:"speed_limit"` - DeviceLimit int64 `json:"device_limit"` + SpeedLimit int64 `json:"speed_limit"` + DeviceLimit int64 `json:"device_limit"` } GetServerUserListRequest { ServerCommon @@ -78,7 +82,6 @@ type ( ServerCommon Traffic []UserTraffic `json:"traffic"` } - ServerPushStatusRequest { ServerCommon Cpu float64 `json:"cpu"` @@ -90,11 +93,25 @@ type ( ServerCommon Users []OnlineUser `json:"users"` } + QueryServerConfigRequest { + ServerID int64 `path:"server_id"` + SecretKey string `form:"secret_key"` + Protocols []string `form:"protocols,omitempty"` + } + QueryServerConfigResponse { + TrafficReportThreshold int64 `json:"traffic_report_threshold"` + IPStrategy string `json:"ip_strategy"` + DNS []NodeDNS `json:"dns"` + Block []string `json:"block"` + Outbound []NodeOutbound `json:"outbound"` + Protocols []Protocol `json:"protocols"` + Total int64 `json:"total"` + } ) @server ( - prefix: v1/server - group: server + prefix: v1/server + group: server middleware: ServerMiddleware ) service ppanel { @@ -119,3 +136,13 @@ service ppanel { post /online (OnlineUsersRequest) } +@server ( + prefix: v2/server + group: server +) +service ppanel { + @doc "Get Server Protocol Config" + @handler QueryServerProtocolConfig + get /:server_id (QueryServerConfigRequest) returns (QueryServerConfigResponse) +} + diff --git a/apis/public/announcement.api b/apis/public/announcement.api index 664ac4c..7122e4e 100644 --- a/apis/public/announcement.api +++ b/apis/public/announcement.api @@ -10,11 +10,10 @@ info ( import "../types.api" - @server ( prefix: v1/public/announcement group: public/announcement - middleware: AuthMiddleware + middleware: AuthMiddleware,DeviceMiddleware ) service ppanel { @doc "Query announcement" diff --git a/apis/public/document.api b/apis/public/document.api index 41d7291..660bea6 100644 --- a/apis/public/document.api +++ b/apis/public/document.api @@ -1,28 +1,27 @@ syntax = "v1" -info( - title: "Document API" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" +info ( + title: "Document API" + desc: "API for ppanel" + author: "Tension" + email: "tension@ppanel.com" + version: "0.0.1" ) import "../types.api" - @server ( - prefix: v1/public/document - group: public/document - middleware: AuthMiddleware + prefix: v1/public/document + group: public/document + middleware: AuthMiddleware,DeviceMiddleware ) - service ppanel { - @doc "Get document list" - @handler QueryDocumentList - get /list returns (QueryDocumentListResponse) + @doc "Get document list" + @handler QueryDocumentList + get /list returns (QueryDocumentListResponse) + + @doc "Get document detail" + @handler QueryDocumentDetail + get /detail (QueryDocumentDetailRequest) returns (Document) +} - @doc "Get document detail" - @handler QueryDocumentDetail - get /detail (QueryDocumentDetailRequest) returns (Document) -} \ No newline at end of file diff --git a/apis/public/order.api b/apis/public/order.api index 0db556f..4e83b0f 100644 --- a/apis/public/order.api +++ b/apis/public/order.api @@ -13,7 +13,7 @@ import "../types.api" @server ( prefix: v1/public/order group: public/order - middleware: AuthMiddleware + middleware: AuthMiddleware,DeviceMiddleware ) service ppanel { @doc "Pre create order" diff --git a/apis/public/payment.api b/apis/public/payment.api index 247f9d4..a4893ab 100644 --- a/apis/public/payment.api +++ b/apis/public/payment.api @@ -10,11 +10,10 @@ info ( import "../types.api" - @server ( prefix: v1/public/payment group: public/payment - middleware: AuthMiddleware + middleware: AuthMiddleware,DeviceMiddleware ) service ppanel { @doc "Get available payment methods" diff --git a/apis/public/portal.api b/apis/public/portal.api index 709e48b..aba8e25 100644 --- a/apis/public/portal.api +++ b/apis/public/portal.api @@ -19,16 +19,20 @@ type ( SubscribeId int64 `json:"subscribe_id"` Quantity int64 `json:"quantity"` Coupon string `json:"coupon,omitempty"` + InviteCode string `json:"invite_code,omitempty"` TurnstileToken string `json:"turnstile_token,omitempty"` } PortalPurchaseResponse { OrderNo string `json:"order_no"` } + GetSubscriptionRequest { + Language string `form:"language"` + } GetSubscriptionResponse { List []Subscribe `json:"list"` } PrePurchaseOrderRequest { - Payment int64 `json:"payment,omitempty"` + Payment int64 `json:"payment,omitempty"` SubscribeId int64 `json:"subscribe_id"` Quantity int64 `json:"quantity"` Coupon string `json:"coupon,omitempty"` @@ -66,6 +70,7 @@ type ( @server ( prefix: v1/public/portal group: public/portal + middleware: DeviceMiddleware ) service ppanel { @doc "Get available payment methods" @@ -74,7 +79,7 @@ service ppanel { @doc "Get Subscription" @handler GetSubscription - get /subscribe returns (GetSubscriptionResponse) + get /subscribe (GetSubscriptionRequest) returns (GetSubscriptionResponse) @doc "Pre Purchase Order" @handler PrePurchaseOrder diff --git a/apis/public/subscribe.api b/apis/public/subscribe.api index 98fd2c2..13024f8 100644 --- a/apis/public/subscribe.api +++ b/apis/public/subscribe.api @@ -9,23 +9,21 @@ info ( ) import "../types.api" - + +type ( + QuerySubscribeListRequest { + Language string `form:"language"` + } +) + @server ( prefix: v1/public/subscribe group: public/subscribe - middleware: AuthMiddleware + middleware: AuthMiddleware,DeviceMiddleware ) service ppanel { @doc "Get subscribe list" @handler QuerySubscribeList - get /list returns (QuerySubscribeListResponse) - - @doc "Get subscribe group list" - @handler QuerySubscribeGroupList - get /group/list returns (QuerySubscribeGroupListResponse) - - @doc "Get application config" - @handler QueryApplicationConfig - get /application/config returns (ApplicationResponse) + get /list (QuerySubscribeListRequest) returns (QuerySubscribeListResponse) } diff --git a/apis/public/ticket.api b/apis/public/ticket.api index b5a846b..69bff62 100644 --- a/apis/public/ticket.api +++ b/apis/public/ticket.api @@ -11,10 +11,9 @@ info ( import "../types.api" type ( - GetUserTicketListResponse { - Total int64 `json:"total"` - List []Ticket `json:"list"` + Total int64 `json:"total"` + List []Ticket `json:"list"` } CreateUserTicketRequest { Title string `json:"title"` @@ -34,17 +33,17 @@ type ( Status *uint8 `json:"status" validate:"required"` } CreateUserTicketFollowRequest { - TicketId int64 `json:"ticket_id"` - From string `json:"from"` - Type uint8 `json:"type"` - Content string `json:"content"` + TicketId int64 `json:"ticket_id"` + From string `json:"from"` + Type uint8 `json:"type"` + Content string `json:"content"` } ) @server ( prefix: v1/public/ticket group: public/ticket - middleware: AuthMiddleware + middleware: AuthMiddleware,DeviceMiddleware ) service ppanel { @doc "Get ticket list" diff --git a/apis/public/user.api b/apis/public/user.api index a2c5a53..1686b32 100644 --- a/apis/public/user.api +++ b/apis/public/user.api @@ -25,16 +25,8 @@ type ( Total int64 `json:"total"` } QueryUserBalanceLogListResponse { - List []UserBalanceLog `json:"list"` - Total int64 `json:"total"` - } - - CommissionLog { - Id int64 `json:"id"` - UserId int64 `json:"user_id"` - OrderNo string `json:"order_no"` - Amount int64 `json:"amount"` - CreatedAt int64 `json:"created_at"` + List []BalanceLog `json:"list"` + Total int64 `json:"total"` } QueryUserCommissionLogListRequest { Page int `form:"page"` @@ -77,47 +69,49 @@ type ( ResetUserSubscribeTokenRequest { UserSubscribeId int64 `json:"user_subscribe_id"` } - GetLoginLogRequest { Page int `form:"page"` Size int `form:"size"` } - GetLoginLogResponse { List []UserLoginLog `json:"list"` Total int64 `json:"total"` } - GetSubscribeLogRequest { Page int `form:"page"` Size int `form:"size"` } - GetSubscribeLogResponse { List []UserSubscribeLog `json:"list"` - Total int64 `json:"total"` + Total int64 `json:"total"` } - - UpdateBindMobileRequest{ - AreaCode string `json:"area_code" validate:"required"` - Mobile string `json:"mobile" validate:"required"` - Code string `json:"code" validate:"required"` + UpdateBindMobileRequest { + AreaCode string `json:"area_code" validate:"required"` + Mobile string `json:"mobile" validate:"required"` + Code string `json:"code" validate:"required"` } - - UpdateBindEmailRequest{ - Email string `json:"email" validate:"required"` + UpdateBindEmailRequest { + Email string `json:"email" validate:"required"` } - VerifyEmailRequest { - Email string `json:"email" validate:"required"` - Code string `json:"code" validate:"required"` + Email string `json:"email" validate:"required"` + Code string `json:"code" validate:"required"` } + + GetDeviceListResponse { + List []UserDevice `json:"list"` + Total int64 `json:"total"` + } + + UnbindDeviceRequest { + Id int64 `json:"id" validate:"required"` + } ) @server ( prefix: v1/public/user group: public/user - middleware: AuthMiddleware + middleware: AuthMiddleware,DeviceMiddleware ) service ppanel { @doc "Query User Info" @@ -207,5 +201,13 @@ service ppanel { @doc "Update Bind Email" @handler UpdateBindEmail put /bind_email (UpdateBindEmailRequest) + + @doc "Get Device List" + @handler GetDeviceList + get /devices returns (GetDeviceListResponse) + + @doc "Unbind Device" + @handler UnbindDevice + put /unbind_device (UnbindDeviceRequest) } diff --git a/apis/swagger_admin.api b/apis/swagger_admin.api index c56b302..e61a903 100644 --- a/apis/swagger_admin.api +++ b/apis/swagger_admin.api @@ -1,26 +1,30 @@ syntax = "v1" -info( - title: "admin API" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" +info ( + title: "admin API" + desc: "API for ppanel" + author: "Tension" + email: "tension@ppanel.com" + version: "0.0.1" ) + import ( - "./admin/system.api" - "./admin/user.api" - "./admin/server.api" - "./admin/subscribe.api" - "./admin/payment.api" - "./admin/coupon.api" - "./admin/order.api" - "./admin/ticket.api" - "./admin/announcement.api" - "./admin/document.api" - "./admin/tool.api" - "./admin/console.api" - "./admin/auth.api" - "./admin/log.api" - "./admin/ads.api" -) \ No newline at end of file + "./admin/system.api" + "./admin/user.api" + "./admin/server.api" + "./admin/subscribe.api" + "./admin/payment.api" + "./admin/coupon.api" + "./admin/order.api" + "./admin/ticket.api" + "./admin/announcement.api" + "./admin/document.api" + "./admin/tool.api" + "./admin/console.api" + "./admin/auth.api" + "./admin/log.api" + "./admin/ads.api" + "./admin/marketing.api" + "./admin/application.api" +) + diff --git a/apis/swagger_app.api b/apis/swagger_app.api deleted file mode 100644 index c4ff674..0000000 --- a/apis/swagger_app.api +++ /dev/null @@ -1,21 +0,0 @@ -syntax = "v1" - -info( - title: "App API" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" -) - -import ( - "./app/auth.api" - "./app/user.api" - "./app/node.api" - "./app/ws.api" - "./app/order.api" - "./app/announcement.api" - "./app/payment.api" - "./app/document.api" - "./app/subscribe.api" -) \ No newline at end of file diff --git a/apis/swagger_common.api b/apis/swagger_common.api index 518b655..ffd0d6d 100644 --- a/apis/swagger_common.api +++ b/apis/swagger_common.api @@ -1,14 +1,15 @@ syntax = "v1" -info( - title: "common API" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" +info ( + title: "common API" + desc: "API for ppanel" + author: "Tension" + email: "tension@ppanel.com" + version: "0.0.1" ) import ( - "./common.api" - "./auth/auth.api" -) \ No newline at end of file + "./common.api" + "./auth/auth.api" +) + diff --git a/apis/swagger_node.api b/apis/swagger_node.api index 6a2ec35..46e77cf 100644 --- a/apis/swagger_node.api +++ b/apis/swagger_node.api @@ -1,11 +1,11 @@ syntax = "v1" -info( - title: "Node API" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" +info ( + title: "Node API" + desc: "API for ppanel" + author: "Tension" + email: "tension@ppanel.com" + version: "0.0.1" ) -import "./node/node.api" \ No newline at end of file +import "./node/node.api" diff --git a/apis/swagger_user.api b/apis/swagger_user.api index bd27b82..099a53f 100644 --- a/apis/swagger_user.api +++ b/apis/swagger_user.api @@ -1,19 +1,21 @@ syntax = "v1" -info( - title: "User API" - desc: "API for ppanel" - author: "Tension" - email: "tension@ppanel.com" - version: "0.0.1" +info ( + title: "User API" + desc: "API for ppanel" + author: "Tension" + email: "tension@ppanel.com" + version: "0.0.1" ) + import ( - "./public/user.api" - "./public/subscribe.api" - "./public/order.api" - "./public/announcement.api" - "./public/ticket.api" - "./public/payment.api" - "./public/document.api" - "./public/portal.api" -) \ No newline at end of file + "./public/user.api" + "./public/subscribe.api" + "./public/order.api" + "./public/announcement.api" + "./public/ticket.api" + "./public/payment.api" + "./public/document.api" + "./public/portal.api" +) + diff --git a/apis/types.api b/apis/types.api index 5d3d9a5..477e150 100644 --- a/apis/types.api +++ b/apis/types.api @@ -14,6 +14,8 @@ type ( Avatar string `json:"avatar"` Balance int64 `json:"balance"` Commission int64 `json:"commission"` + ReferralPercentage uint8 `json:"referral_percentage"` + OnlyFirstPurchase bool `json:"only_first_purchase"` GiftAmount int64 `json:"gift_amount"` Telegram int64 `json:"telegram"` ReferCode string `json:"refer_code"` @@ -63,6 +65,8 @@ type ( SubscribePath string `json:"subscribe_path"` SubscribeDomain string `json:"subscribe_domain"` PanDomain bool `json:"pan_domain"` + UserAgentLimit bool `json:"user_agent_limit"` + UserAgentList string `json:"user_agent_list"` } VerifyCodeConfig { VerifyCodeExpireTime int64 `json:"verify_code_expire_time"` @@ -111,6 +115,7 @@ type ( AuthConfig { Mobile MobileAuthenticateConfig `json:"mobile"` Email EmailAuthticateConfig `json:"email"` + Device DeviceAuthticateConfig `json:"device"` Register PubilcRegisterConfig `json:"register"` } PubilcRegisterConfig { @@ -130,6 +135,14 @@ type ( EnableDomainSuffix bool `json:"enable_domain_suffix"` 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 { StopRegister bool `json:"stop_register"` EnableTrial bool `json:"enable_trial"` @@ -148,9 +161,27 @@ type ( EnableResetPasswordVerify bool `json:"enable_reset_password_verify"` } NodeConfig { - NodeSecret string `json:"node_secret"` - NodePullInterval int64 `json:"node_pull_interval"` - NodePushInterval int64 `json:"node_push_interval"` + NodeSecret string `json:"node_secret"` + NodePullInterval int64 `json:"node_pull_interval"` + NodePushInterval int64 `json:"node_push_interval"` + TrafficReportThreshold int64 `json:"traffic_report_threshold"` + IPStrategy string `json:"ip_strategy"` + DNS []NodeDNS `json:"dns"` + Block []string `json:"block"` + Outbound []NodeOutbound `json:"outbound"` + } + NodeDNS { + Proto string `json:"proto"` + Address string `json:"address"` + Domains []string `json:"domains"` + } + NodeOutbound { + Name string `json:"name"` + Protocol string `json:"protocol"` + Address string `json:"address"` + Port int64 `json:"port"` + Password string `json:"password"` + Rules []string `json:"rules"` } InviteConfig { ForcedInvite bool `json:"forced_invite"` @@ -181,6 +212,7 @@ type ( Subscribe { Id int64 `json:"id"` Name string `json:"name"` + Language string `json:"language"` Description string `json:"description"` UnitPrice int64 `json:"unit_price"` UnitTime string `json:"unit_time"` @@ -191,9 +223,8 @@ type ( SpeedLimit int64 `json:"speed_limit"` DeviceLimit int64 `json:"device_limit"` Quota int64 `json:"quota"` - GroupId int64 `json:"group_id"` - ServerGroup []int64 `json:"server_group"` - Server []int64 `json:"server"` + Nodes []int64 `json:"nodes"` + NodeTags []string `json:"node_tags"` Show bool `json:"show"` Sell bool `json:"sell"` Sort int64 `json:"sort"` @@ -246,6 +277,14 @@ type ( SecurityConfig SecurityConfig `json:"security_config"` } Tuic { + Port int `json:"port" validate:"required"` + DisableSNI bool `json:"disable_sni"` + ReduceRtt bool `json:"reduce_rtt"` + UDPRelayMode string `json:"udp_relay_mode"` + CongestionController string `json:"congestion_controller"` + SecurityConfig SecurityConfig `json:"security_config"` + } + AnyTLS { Port int `json:"port" validate:"required"` SecurityConfig SecurityConfig `json:"security_config"` } @@ -264,37 +303,37 @@ type ( Host string `json:"host"` ServiceName string `json:"service_name"` } - Server { - Id int64 `json:"id"` - Tags []string `json:"tags"` - Country string `json:"country"` - City string `json:"city"` - Name string `json:"name"` - ServerAddr string `json:"server_addr"` - RelayMode string `json:"relay_mode"` - RelayNode []NodeRelay `json:"relay_node"` - SpeedLimit int `json:"speed_limit"` - TrafficRatio float32 `json:"traffic_ratio"` - GroupId int64 `json:"group_id"` - Protocol string `json:"protocol"` - Config interface{} `json:"config"` - Enable *bool `json:"enable"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` - Status *NodeStatus `json:"status"` - Sort int64 `json:"sort"` - } - OnlineUser { - SID int64 `json:"uid"` - IP string `json:"ip"` - } - NodeStatus { - Online interface{} `json:"online"` - Cpu float64 `json:"cpu"` - Mem float64 `json:"mem"` - Disk float64 `json:"disk"` - UpdatedAt int64 `json:"updated_at"` - } + // Server { + // Id int64 `json:"id"` + // Tags []string `json:"tags"` + // Country string `json:"country"` + // City string `json:"city"` + // Name string `json:"name"` + // ServerAddr string `json:"server_addr"` + // RelayMode string `json:"relay_mode"` + // RelayNode []NodeRelay `json:"relay_node"` + // SpeedLimit int `json:"speed_limit"` + // TrafficRatio float32 `json:"traffic_ratio"` + // GroupId int64 `json:"group_id"` + // Protocol string `json:"protocol"` + // Config interface{} `json:"config"` + // Enable *bool `json:"enable"` + // CreatedAt int64 `json:"created_at"` + // UpdatedAt int64 `json:"updated_at"` + // Status *NodeStatus `json:"status"` + // Sort int64 `json:"sort"` + // } + // OnlineUser { + // SID int64 `json:"uid"` + // IP string `json:"ip"` + // } + // NodeStatus { + // Online interface{} `json:"online"` + // Cpu float64 `json:"cpu"` + // Mem float64 `json:"mem"` + // Disk float64 `json:"disk"` + // UpdatedAt int64 `json:"updated_at"` + // } ServerGroup { Id int64 `json:"id"` Name string `json:"name"` @@ -426,7 +465,7 @@ type ( Subscribe Subscribe `json:"subscribe"` StartTime int64 `json:"start_time"` ExpireTime int64 `json:"expire_time"` - FinishedAt int64 `json:"finished_at"` + FinishedAt int64 `json:"finished_at"` ResetTime int64 `json:"reset_time"` Traffic int64 `json:"traffic"` Download int64 `json:"download"` @@ -436,15 +475,6 @@ type ( CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` } - UserBalanceLog { - Id int64 `json:"id"` - UserId int64 `json:"user_id"` - Amount int64 `json:"amount"` - Type uint8 `json:"type"` - OrderId int64 `json:"order_id"` - Balance int64 `json:"balance"` - CreatedAt int64 `json:"created_at"` - } UserAffiliate { Avatar string `json:"avatar"` Identifier string `json:"identifier"` @@ -465,14 +495,6 @@ type ( Port int `json:"port"` Prefix string `json:"prefix"` } - ApplicationConfig { - AppId int64 `json:"app_id"` - EncryptionKey string `json:"encryption_key"` - EncryptionMethod string `json:"encryption_method"` - Domains []string `json:"domains" validate:"required"` - StartupPicture string `json:"startup_picture"` - StartupPictureSkipTime int64 `json:"startup_picture_skip_time"` - } UserDevice { Id int64 `json:"id"` Ip string `json:"ip"` @@ -505,11 +527,13 @@ type ( } ServerRuleGroup { Id int64 `json:"id"` - Icon string `json:"icon"` + Icon string `json:"icon"` Name string `json:"name" validate:"required"` + Type string `json:"type"` Tags []string `json:"tags"` Rules string `json:"rules"` Enable bool `json:"enable"` + Default bool `json:"default"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` } @@ -520,7 +544,7 @@ type ( Token string `json:"token"` IP string `json:"ip"` UserAgent string `json:"user_agent"` - CreatedAt int64 `json:"created_at"` + Timestamp int64 `json:"timestamp"` } UserLoginLog { Id int64 `json:"id"` @@ -528,18 +552,17 @@ type ( LoginIP string `json:"login_ip"` UserAgent string `json:"user_agent"` Success bool `json:"success"` - CreatedAt int64 `json:"created_at"` + Timestamp int64 `json:"timestamp"` } MessageLog { - Id int64 `json:"id"` - Type string `json:"type"` - Platform string `json:"platform"` - To string `json:"to"` - Subject string `json:"subject"` - Content string `json:"content"` - Status int `json:"status"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` + Id int64 `json:"id"` + Type uint8 `json:"type"` + Platform string `json:"platform"` + To string `json:"to"` + Subject string `json:"subject"` + Content interface{} `json:"content"` + Status uint8 `json:"status"` + CreatedAt int64 `json:"created_at"` } Ads { Id int `json:"id"` @@ -557,8 +580,8 @@ type ( //public order PurchaseOrderRequest { SubscribeId int64 `json:"subscribe_id"` - Quantity int64 `json:"quantity"` - Payment int64 `json:"payment,omitempty"` + Quantity int64 `json:"quantity" validate:"required,gt=0"` + Payment int64 `json:"payment,omitempty"` Coupon string `json:"coupon,omitempty"` } PreOrderResponse { @@ -707,46 +730,120 @@ type ( Telephone string `json:"telephone"` Address string `json:"address"` } - QueryUserAffiliateCountResponse { Registers int64 `json:"registers"` TotalCommission int64 `json:"total_commission"` } - - AppUserSubcbribe{ - Id int64 `json:"id"` - Name string `json:"name"` - Upload int64 `json:"upload"` - Traffic int64 `json:"traffic"` - Download int64 `json:"download"` - DeviceLimit int64 `json:"device_limit"` - StartTime string `json:"start_time"` - ExpireTime string `json:"expire_time"` - List []AppUserSubscbribeNode `json:"list"` + AppUserSubcbribe { + Id int64 `json:"id"` + Name string `json:"name"` + Upload int64 `json:"upload"` + Traffic int64 `json:"traffic"` + Download int64 `json:"download"` + DeviceLimit int64 `json:"device_limit"` + StartTime string `json:"start_time"` + ExpireTime string `json:"expire_time"` + List []AppUserSubscbribeNode `json:"list"` } - - AppUserSubscbribeNode{ - Id int64 `json:"id"` - Name string `json:"name"` - Uuid string `json:"uuid"` - Protocol string `json:"protocol"` - RelayMode string `json:"relay_mode"` - RelayNode string `json:"relay_node"` - ServerAddr string `json:"server_addr"` - SpeedLimit int `json:"speed_limit"` - Tags []string `json:"tags"` - Traffic int64 `json:"traffic"` - TrafficRatio float64 `json:"traffic_ratio"` - Upload int64 `json:"upload"` - Config string `json:"config"` - Country string `json:"country"` - City string `json:"city"` - Latitude string `json:"latitude"` - Longitude string `json:"longitude"` - LatitudeCountry string `json:"latitudeCountry"` - LongitudeCountry string `json:"longitudeCountry"` - CreatedAt int64 `json:"created_at"` - Download int64 `json:"download"` + AppUserSubscbribeNode { + Id int64 `json:"id"` + Name string `json:"name"` + Uuid string `json:"uuid"` + Protocol string `json:"protocol"` + RelayMode string `json:"relay_mode"` + RelayNode string `json:"relay_node"` + ServerAddr string `json:"server_addr"` + SpeedLimit int `json:"speed_limit"` + Tags []string `json:"tags"` + Traffic int64 `json:"traffic"` + TrafficRatio float64 `json:"traffic_ratio"` + Upload int64 `json:"upload"` + Config string `json:"config"` + Country string `json:"country"` + City string `json:"city"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + CreatedAt int64 `json:"created_at"` + Download int64 `json:"download"` + } + DownloadLink { + IOS string `json:"ios,omitempty"` + Android string `json:"android,omitempty"` + Windows string `json:"windows,omitempty"` + Mac string `json:"mac,omitempty"` + Linux string `json:"linux,omitempty"` + Harmony string `json:"harmony,omitempty"` + } + ResetSubscribeTrafficLog { + Id int64 `json:"id"` + Type uint16 `json:"type"` + UserSubscribeId int64 `json:"user_subscribe_id"` + OrderNo string `json:"order_no,omitempty"` + Timestamp int64 `json:"timestamp"` + } + BalanceLog { + Type uint16 `json:"type"` + UserId int64 `json:"user_id"` + Amount int64 `json:"amount"` + OrderNo string `json:"order_no,omitempty"` + Balance int64 `json:"balance"` + Timestamp int64 `json:"timestamp"` + } + CommissionLog { + Type uint16 `json:"type"` + UserId int64 `json:"user_id"` + Amount int64 `json:"amount"` + OrderNo string `json:"order_no"` + Timestamp int64 `json:"timestamp"` + } + Protocol { + Type string `json:"type"` + Port uint16 `json:"port"` + Enable bool `json:"enable"` + Security string `json:"security,omitempty"` + SNI string `json:"sni,omitempty"` + AllowInsecure bool `json:"allow_insecure,omitempty"` + Fingerprint string `json:"fingerprint,omitempty"` + RealityServerAddr string `json:"reality_server_addr,omitempty"` + RealityServerPort int `json:"reality_server_port,omitempty"` + RealityPrivateKey string `json:"reality_private_key,omitempty"` + RealityPublicKey string `json:"reality_public_key,omitempty"` + RealityShortId string `json:"reality_short_id,omitempty"` + Transport string `json:"transport,omitempty"` + Host string `json:"host,omitempty"` + Path string `json:"path,omitempty"` + ServiceName string `json:"service_name,omitempty"` + Cipher string `json:"cipher,omitempty"` + ServerKey string `json:"server_key,omitempty"` + Flow string `json:"flow,omitempty"` + HopPorts string `json:"hop_ports,omitempty"` + HopInterval int `json:"hop_interval,omitempty"` + ObfsPassword string `json:"obfs_password,omitempty"` + DisableSNI bool `json:"disable_sni,omitempty"` + ReduceRtt bool `json:"reduce_rtt,omitempty"` + UDPRelayMode string `json:"udp_relay_mode,omitempty"` + CongestionController string `json:"congestion_controller,omitempty"` + Multiplex string `json:"multiplex,omitempty"` // mux, eg: off/low/medium/high + PaddingScheme string `json:"padding_scheme,omitempty"` // padding scheme + UpMbps int `json:"up_mbps,omitempty"` // upload speed limit + DownMbps int `json:"down_mbps,omitempty"` // download speed limit + Obfs string `json:"obfs,omitempty"` // obfs, 'none', 'http', 'tls' + ObfsHost string `json:"obfs_host,omitempty"` // obfs host + ObfsPath string `json:"obfs_path,omitempty"` // obfs path + XhttpMode string `json:"xhttp_mode,omitempty"` // xhttp mode + XhttpExtra string `json:"xhttp_extra,omitempty"` // xhttp extra path + Encryption string `json:"encryption,omitempty"` // encryption,'none', 'mlkem768x25519plus' + EncryptionMode string `json:"encryption_mode,omitempty"` // encryption mode,'native', 'xorpub', 'random' + EncryptionRtt string `json:"encryption_rtt,omitempty"` // encryption rtt,'0rtt', '1rtt' + EncryptionTicket string `json:"encryption_ticket,omitempty"` // encryption ticket + EncryptionServerPadding string `json:"encryption_server_padding,omitempty"` // encryption server padding + EncryptionPrivateKey string `json:"encryption_private_key,omitempty"` // encryption private key + EncryptionClientPadding string `json:"encryption_client_padding,omitempty"` // encryption client padding + EncryptionPassword string `json:"encryption_password,omitempty"` // encryption password + Ratio float64 `json:"ratio,omitempty"` // Traffic ratio, default is 1 + CertMode string `json:"cert_mode,omitempty"` // Certificate mode, `none`|`http`|`dns`|`self` + CertDNSProvider string `json:"cert_dns_provider,omitempty"` // DNS provider for certificate + CertDNSEnv string `json:"cert_dns_env,omitempty"` // Environment for DNS provider } ) diff --git a/cmd/run.go b/cmd/run.go index a96d3d8..13a0a1c 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/perfect-panel/server/pkg/constant" + "log" "os" "os/signal" @@ -12,18 +14,17 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" - "github.com/perfect-panel/ppanel-server/initialize" - "github.com/perfect-panel/ppanel-server/internal" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/conf" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/orm" - "github.com/perfect-panel/ppanel-server/pkg/service" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/queue" - "github.com/perfect-panel/ppanel-server/scheduler" + "github.com/perfect-panel/server/initialize" + "github.com/perfect-panel/server/internal" + "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/perfect-panel/server/pkg/orm" + "github.com/perfect-panel/server/pkg/service" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/queue" + "github.com/perfect-panel/server/scheduler" "github.com/spf13/cobra" "gopkg.in/yaml.v3" ) @@ -40,7 +41,7 @@ var startCmd = &cobra.Command{ Use: "run", Short: "start PPanel", Run: func(cmd *cobra.Command, args []string) { - fmt.Println("[PPanel version] v" + constant.Version) + fmt.Println("[PPanel version] v" + fmt.Sprintf("%s(%s)", constant.Version, constant.BuildTime)) run() }, } diff --git a/cmd/version.go b/cmd/version.go index 804c976..73c3025 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -3,7 +3,8 @@ package cmd import ( "fmt" - "github.com/perfect-panel/ppanel-server/internal/config" + "github.com/perfect-panel/server/pkg/constant" + "github.com/spf13/cobra" ) @@ -11,6 +12,6 @@ var versionCmd = &cobra.Command{ Use: "version", Short: "PPanel version", Run: func(cmd *cobra.Command, args []string) { - fmt.Println("[PPanel version] " + config.Version) + fmt.Println("[PPanel version] " + constant.Version + " (" + constant.BuildTime + ")") }, } diff --git a/doc/config-zh.md b/doc/config-zh.md index 1f2e409..55b7adf 100644 --- a/doc/config-zh.md +++ b/doc/config-zh.md @@ -1,88 +1,161 @@ -### 配置文件说明 +# PPanel 配置指南 -#### 1. 配置文件路径 +本文件为 PPanel 应用程序的配置文件提供全面指南。配置文件采用 YAML 格式,定义了服务器、日志、数据库、Redis 和管理员访问的相关设置。 -配置文件默认路径为:`./etc/ppanel.yaml`,可通过启动参数 `--config` 指定配置文件路径。 +## 1. 配置文件概述 -#### 2. 配置文件格式 - - 配置文件为yaml格式,支持注释,命名为xxx.yaml。 +- **默认路径**:`./etc/ppanel.yaml` +- **自定义路径**:通过启动参数 `--config` 指定配置文件路径。 +- **格式**:YAML 格式,支持注释,文件名需以 `.yaml` 结尾。 + +## 2. 配置文件结构 + +以下是配置文件示例,包含默认值和说明: ```yaml -# 配置文件示例 -Host: # 服务监听地址,默认: 0.0.0.0 -Port: # 服务监听端口,默认: 8080 -Debug: # 是否开启调试模式,开启后无法使用后台日志功能, 默认: false -JwtAuth: # JWT认证配置 - AccessSecret: # 访问令牌密钥, 默认: 随机生成 - AccessExpire: # 访问令牌过期时间,单位秒, 默认: 604800 -Logger: # 日志配置 - FilePath: # 日志文件路径, 默认: ./ppanel.log - MaxSize: # 日志文件最大大小,单位MB, 默认: 50 - MaxBackup: # 日志文件最大备份数, 默认: 3 - MaxAge: # 日志文件最大保存时间,单位天, 默认: 30 - Compress: # 是否压缩日志文件, 默认: true - Level: # 日志级别, 默认: info, 可选: debug, info, warn, error, panic, panic, fatal -MySQL: - Addr: # MySQL地址, 必填 - Username: # MySQL用户名, 必填 - Password: # MySQL密码, 必填 - Dbname: # MySQL数据库名, 必填 - Config: # Mysql配置默认值 charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai - MaxIdleConns: # 最大空闲连接数, 默认: 10 - MaxOpenConns: # 最大打开连接数, 默认: 100 - LogMode: # 日志级别, 默认: info, 可选: debug, error, warn, info - LogZap: # 是否使用zap日志记录sql, 默认: true - SlowThreshold: # 慢查询阈值,单位毫秒, 默认: 1000 -Redis: - Host: # Redis地址, 默认:localhost:6379 - Pass: # Redis密码, 默认: "" - DB: # Redis数据库, 默认: 0 - -Administer: - Email: # 后台登录邮箱, 默认: admin@ppanel.dev - Password: # 后台登录密码, 默认: password - +# PPanel 配置文件 +Host: "0.0.0.0" # 服务监听地址 +Port: 8080 # 服务监听端口 +Debug: false # 是否开启调试模式(禁用后台日志) +JwtAuth: # JWT 认证配置 + AccessSecret: "" # 访问令牌密钥(为空时随机生成) + AccessExpire: 604800 # 访问令牌过期时间(秒) +Logger: # 日志配置 + ServiceName: "" # 日志服务标识名称 + Mode: "console" # 日志输出模式(console、file、volume) + Encoding: "json" # 日志格式(json、plain) + TimeFormat: "2006-01-02T15:04:05.000Z07:00" # 自定义时间格式 + Path: "logs" # 日志文件目录 + Level: "info" # 日志级别(info、error、severe) + Compress: false # 是否压缩日志文件 + KeepDays: 7 # 日志保留天数 + StackCooldownMillis: 100 # 堆栈日志冷却时间(毫秒) + MaxBackups: 3 # 最大日志备份数 + MaxSize: 50 # 最大日志文件大小(MB) + Rotation: "daily" # 日志轮转策略(daily、size) +MySQL: # MySQL 数据库配置 + Addr: "" # MySQL 地址(必填) + Username: "" # MySQL 用户名(必填) + Password: "" # MySQL 密码(必填) + Dbname: "" # MySQL 数据库名(必填) + Config: "charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai" # MySQL 连接参数 + MaxIdleConns: 10 # 最大空闲连接数 + MaxOpenConns: 100 # 最大打开连接数 + LogMode: "info" # 日志级别(debug、error、warn、info) + LogZap: true # 是否使用 Zap 记录 SQL 日志 + SlowThreshold: 1000 # 慢查询阈值(毫秒) +Redis: # Redis 配置 + Host: "localhost:6379" # Redis 地址 + Pass: "" # Redis 密码 + DB: 0 # Redis 数据库索引 +Administer: # 管理员登录配置 + Email: "admin@ppanel.dev" # 管理员登录邮箱 + Password: "password" # 管理员登录密码 ``` -#### 3. 配置文件说明 +## 3. 配置项说明 -- `Host`: 服务监听地址,默认: **0.0.0.0** -- `Port`: 服务监听端口,默认: **8080** -- `Debug`: 是否开启调试模式,开启后无法使用后台日志功能, 默认: **false** -- `JwtAuth`: JWT认证配置 - - `AccessSecret`: 访问令牌密钥, 默认: **随机生成** - - `AccessExpire`: 访问令牌过期时间,单位秒, 默认: **604800** -- `Logger`: 日志配置 -- `FilePath`: 日志文件路径, 默认: **./ppanel.log** -- `MaxSize`: 日志文件最大大小,单位MB, 默认: **50** -- `MaxBackup`: 日志文件最大备份数, 默认: **3** -- `MaxAge`: 日志文件最大保存时间,单位天, 默认: **30** -- `Compress`: 是否压缩日志文件, 默认: **true** -- `Level`: 日志级别, 默认: **info**, 可选: **debug, info, warn, error, panic, panic, fatal** -- `MySQL`: MySQL配置 - - `Addr`: MySQL地址, 必填 - - `Username`: MySQL用户名, 必填 - - `Password`: MySQL密码, 必填 - - `Dbname`: MySQL数据库名, 必填 - - `Config`: Mysql配置默认值 charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai - - `MaxIdleConns`: 最大空闲连接数, 默认: **10** - - `MaxOpenConns`: 最大打开连接数, 默认: **100** - - `LogMode`: 日志级别, 默认: **info**, 可选: **debug, error, warn, info** - - `LogZap`: 是否使用zap日志记录sql, 默认: **true** - - `SlowThreshold`: 慢查询阈值,单位毫秒, 默认: **1000** -- `Redis`: Redis配置 -- `Host`: Redis地址, 默认: **localhost:6379** -- `Pass`: Redis密码, 默认: **""** -- `DB`: Redis数据库, 默认: **0** -- `Administer`: 后台登录配置 - - `Email`: 后台登录邮箱, 默认: **admin@ppanel.dev** - - `Password`: 后台登录密码, 默认: **password** +### 3.1 服务器设置 -#### 4. 环境变量 +- **`Host`**:服务监听的地址。 + - 默认:`0.0.0.0`(监听所有网络接口)。 +- **`Port`**:服务监听的端口。 + - 默认:`8080`。 +- **`Debug`**:是否开启调试模式,开启后禁用后台日志功能。 + - 默认:`false`。 -支持的环境变量如下: +### 3.2 JWT 认证 (`JwtAuth`) -| 环境变量 | 配置项 | 示例 | -|--------------|---------|:-------------------------------------------| -| PPANEL_DB | MySQL配置 | root:password@tcp(localhost:3306)/vpnboard | -| PPANEL_REDIS | Redis配置 | redis://localhost:6379" | +- **`AccessSecret`**:访问令牌的密钥。 + - 默认:为空时随机生成。 +- **`AccessExpire`**:令牌过期时间(秒)。 + - 默认:`604800`(7天)。 + +### 3.3 日志配置 (`Logger`) + +- **`ServiceName`**:日志的服务标识名称,在 `volume` 模式下用作日志文件名。 + - 默认:`""`。 +- **`Mode`**:日志输出方式。 + - 选项:`console`(标准输出/错误输出)、`file`(写入指定目录)、`volume`(Docker 卷)。 + - 默认:`console`。 +- **`Encoding`**:日志格式。 + - 选项:`json`(结构化 JSON)、`plain`(纯文本,带颜色)。 + - 默认:`json`。 +- **`TimeFormat`**:日志时间格式。 + - 默认:`2006-01-02T15:04:05.000Z07:00`。 +- **`Path`**:日志文件存储目录。 + - 默认:`logs`。 +- **`Level`**:日志过滤级别。 + - 选项:`info`(记录所有日志)、`error`(仅错误和严重日志)、`severe`(仅严重日志)。 + - 默认:`info`。 +- **`Compress`**:是否压缩日志文件(仅在 `file` 模式下生效)。 + - 默认:`false`。 +- **`KeepDays`**:日志文件保留天数。 + - 默认:`7`。 +- **`StackCooldownMillis`**:堆栈日志冷却时间(毫秒),防止日志过多。 + - 默认:`100`。 +- **`MaxBackups`**:最大日志备份数量(仅在 `size` 轮转时生效)。 + - 默认:`3`。 +- **`MaxSize`**:日志文件最大大小(MB,仅在 `size` 轮转时生效)。 + - 默认:`50`。 +- **`Rotation`**:日志轮转策略。 + - 选项:`daily`(按天轮转)、`size`(按大小轮转)。 + - 默认:`daily`。 + +### 3.4 MySQL 数据库 (`MySQL`) + +- **`Addr`**:MySQL 服务器地址。 + - 必填。 +- **`Username`**:MySQL 用户名。 + - 必填。 +- **`Password`**:MySQL 密码。 + - 必填。 +- **`Dbname`**:MySQL 数据库名。 + - 必填。 +- **`Config`**:MySQL 连接参数。 + - 默认:`charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai`。 +- **`MaxIdleConns`**:最大空闲连接数。 + - 默认:`10`。 +- **`MaxOpenConns`**:最大打开连接数。 + - 默认:`100`。 +- **`LogMode`**:SQL 日志级别。 + - 选项:`debug`、`error`、`warn`、`info`。 + - 默认:`info`。 +- **`LogZap`**:是否使用 Zap 记录 SQL 日志。 + - 默认:`true`。 +- **`SlowThreshold`**:慢查询阈值(毫秒)。 + - 默认:`1000`。 + +### 3.5 Redis 配置 (`Redis`) + +- **`Host`**:Redis 服务器地址。 + - 默认:`localhost:6379`。 +- **`Pass`**:Redis 密码。 + - 默认:`""`(无密码)。 +- **`DB`**:Redis 数据库索引。 + - 默认:`0`。 + +### 3.6 管理员登录 (`Administer`) + +- **`Email`**:管理员登录邮箱。 + - 默认:`admin@ppanel.dev`。 +- **`Password`**:管理员登录密码。 + - 默认:`password`。 + +## 4. 环境变量 + +以下环境变量可用于覆盖配置文件中的设置: + +| 环境变量 | 配置项 | 示例值 | +|----------------|----------|----------------------------------------------| +| `PPANEL_DB` | MySQL 配置 | `root:password@tcp(localhost:3306)/vpnboard` | +| `PPANEL_REDIS` | Redis 配置 | `redis://localhost:6379` | + +## 5. 最佳实践 + +- **安全性**:生产环境中避免使用默认的 `Administer` 凭据,更新 `Email` 和 `Password` 为安全值。 +- **日志**:生产环境中建议使用 `file` 或 `volume` 模式持久化日志,将 `Level` 设置为 `error` 或 `severe` 以减少日志量。 +- **数据库**:确保 `MySQL` 和 `Redis` 凭据安全,避免在版本控制中暴露。 +- **JWT**:为 `JwtAuth` 的 `AccessSecret` 设置强密钥以增强安全性。 + +如需进一步帮助,请参考 PPanel 官方文档或联系支持团队。 \ No newline at end of file diff --git a/doc/config.md b/doc/config.md index ebb52c1..99b6b3c 100644 --- a/doc/config.md +++ b/doc/config.md @@ -1,116 +1,164 @@ -### Configuration File Instructions -#### Configuration File Path +# PPanel Configuration Guide -The default configuration file path is ./etc/ppanel.yaml. You can specify a custom path using the --config startup parameter. +This document provides a comprehensive guide to the configuration file for the PPanel application. The configuration +file is in YAML format and defines settings for the server, logging, database, Redis, and admin access. -#### Configuration File Format +## 1. Configuration File Overview -The configuration file uses the YAML format, supports comments, and should be named xxx.yaml. +- **Default Path**: `./etc/ppanel.yaml` +- **Custom Path**: Specify a custom path using the `--config` startup parameter. +- **Format**: YAML, supports comments, and must be named with a `.yaml` extension. + +## 2. Configuration File Structure + +Below is an example of the configuration file with default values and explanations: ```yaml -# Sample Configuration File -Host: # Service listening address, default: 0.0.0.0 -Port: # Service listening port, default: 8080 -Debug: # Enable debug mode; disables backend logging when enabled, default: false -JwtAuth: # JWT authentication settings - AccessSecret: # Access token secret, default: randomly generated - AccessExpire: # Access token expiration time in seconds, default: 604800 -Logger: # Logging configuration - FilePath: # Log file path, default: ./ppanel.log - MaxSize: # Maximum log file size in MB, default: 50 - MaxBackup: # Maximum number of log file backups, default: 3 - MaxAge: # Maximum log file retention time in days, default: 30 - Compress: # Whether to compress log files, default: true - Level: # Logging level, default: info; options: debug, info, warn, error, panic, fatal -MySQL: - Addr: # MySQL address, required - Username: # MySQL username, required - Password: # MySQL password, required - Dbname: # MySQL database name, required - Config: # MySQL configuration, default: charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai - MaxIdleConns: # Maximum idle connections, default: 10 - MaxOpenConns: # Maximum open connections, default: 100 - LogMode: # Log level, default: info; options: debug, error, warn, info - LogZap: # Whether to use zap for SQL logging, default: true - SlowThreshold: # Slow query threshold in milliseconds, default: 1000 -Redis: - Host: # Redis address, default: localhost:6379 - Pass: # Redis password, default: "" - DB: # Redis database, default: 0 - -Administer: - Email: # Admin login email, default: admin@ppanel.dev - Password: # Admin login password, default: password +# PPanel Configuration +Host: "0.0.0.0" # Server listening address +Port: 8080 # Server listening port +Debug: false # Enable debug mode (disables background logging) +JwtAuth: # JWT authentication settings + AccessSecret: "" # Access token secret (randomly generated if empty) + AccessExpire: 604800 # Access token expiration (seconds) +Logger: # Logging configuration + ServiceName: "" # Service name for log identification + Mode: "console" # Log output mode (console, file, volume) + Encoding: "json" # Log format (json, plain) + TimeFormat: "2006-01-02T15:04:05.000Z07:00" # Custom time format + Path: "logs" # Log file directory + Level: "info" # Log level (info, error, severe) + Compress: false # Enable log compression + KeepDays: 7 # Log retention period (days) + StackCooldownMillis: 100 # Stack trace cooldown (milliseconds) + MaxBackups: 3 # Maximum number of log backups + MaxSize: 50 # Maximum log file size (MB) + Rotation: "daily" # Log rotation strategy (daily, size) +MySQL: # MySQL database configuration + Addr: "" # MySQL address (required) + Username: "" # MySQL username (required) + Password: "" # MySQL password (required) + Dbname: "" # MySQL database name (required) + Config: "charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai" # MySQL connection parameters + MaxIdleConns: 10 # Maximum idle connections + MaxOpenConns: 100 # Maximum open connections + LogMode: "info" # Log level (debug, error, warn, info) + LogZap: true # Enable Zap logging for SQL + SlowThreshold: 1000 # Slow query threshold (milliseconds) +Redis: # Redis configuration + Host: "localhost:6379" # Redis address + Pass: "" # Redis password + DB: 0 # Redis database index +Administer: # Admin login configuration + Email: "admin@ppanel.dev" # Admin login email + Password: "password" # Admin login password ``` -#### 3.Configuration Descriptions +## 3. Configuration Details -- Host: Service listening address, default: 0.0.0.0 +### 3.1 Server Settings -- Port: Service listening port, default: 8080 -- Debug: Enable debug mode; disables backend logging when enabled, default: false +- **`Host`**: Address the server listens on. + - Default: `0.0.0.0` (all network interfaces). +- **`Port`**: Port the server listens on. + - Default: `8080`. +- **`Debug`**: Enables debug mode, disabling background logging. + - Default: `false`. -- JwtAuth: JWT authentication settings +### 3.2 JWT Authentication (`JwtAuth`) - - AccessSecret: Access token secret, default: randomly generated - - - AccessExpire: Access token expiration time in seconds, default: 604800 - - - Logger: Logging configuration - - - FilePath: Log file path, default: ./ppanel.log - - - MaxSize: Maximum log file size in MB, default: 50 - - - MaxBackup: Maximum number of log file backups, default: 3 - - - MaxAge: Maximum log file retention time in days, default: 30 - - - Compress: Whether to compress log files, default: true - - - Level: Logging level, default: info; options: debug, info, warn, error, panic, fatal - -- MySQL: MySQL configuration - - - Addr: MySQL address, required - - - Username: MySQL username, required +- **`AccessSecret`**: Secret key for access tokens. + - Default: Randomly generated if not specified. +- **`AccessExpire`**: Token expiration time in seconds. + - Default: `604800` (7 days). - - Password: MySQL password, required +### 3.3 Logging (`Logger`) - - Dbname: MySQL database name, required - - - Config: MySQL configuration, default: charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai - - - MaxIdleConns: Maximum idle connections, default: 10 - - - MaxOpenConns: Maximum open connections, default: 100 - - - LogMode: Log level, default: info; options: debug, error, warn, info - - - LogZap: Whether to use zap for SQL logging, default: true - - - SlowThreshold: Slow query threshold in milliseconds, default: 1000 - -- Redis: Redis configuration +- **`ServiceName`**: Identifier for logs, used as the log filename in `volume` mode. + - Default: `""`. +- **`Mode`**: Log output destination. + - Options: `console` (stdout/stderr), `file` (to a directory), `volume` (Docker volume). + - Default: `console`. +- **`Encoding`**: Log format. + - Options: `json` (structured JSON), `plain` (plain text with colors). + - Default: `json`. +- **`TimeFormat`**: Custom time format for logs. + - Default: `2006-01-02T15:04:05.000Z07:00`. +- **`Path`**: Directory for log files. + - Default: `logs`. +- **`Level`**: Log filtering level. + - Options: `info` (all logs), `error` (error and severe), `severe` (severe only). + - Default: `info`. +- **`Compress`**: Enable compression for log files (only in `file` mode). + - Default: `false`. +- **`KeepDays`**: Retention period for log files (in days). + - Default: `7`. +- **`StackCooldownMillis`**: Cooldown for stack trace logging to prevent log flooding. + - Default: `100`. +- **`MaxBackups`**: Maximum number of log backups (for `size` rotation). + - Default: `3`. +- **`MaxSize`**: Maximum log file size in MB (for `size` rotation). + - Default: `50`. +- **`Rotation`**: Log rotation strategy. + - Options: `daily` (rotate daily), `size` (rotate by size). + - Default: `daily`. - - Host: Redis address, default: localhost:6379 - - - Pass: Redis password, default: "" - - - DB: Redis database, default: 0 - -- Administer: Admin login configuration +### 3.4 MySQL Database (`MySQL`) - - Email: Admin login email, default: admin@ppanel.dev +- **`Addr`**: MySQL server address. + - Required. +- **`Username`**: MySQL username. + - Required. +- **`Password`**: MySQL password. + - Required. +- **`Dbname`**: MySQL database name. + - Required. +- **`Config`**: MySQL connection parameters. + - Default: `charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai`. +- **`MaxIdleConns`**: Maximum idle connections. + - Default: `10`. +- **`MaxOpenConns`**: Maximum open connections. + - Default: `100`. +- **`LogMode`**: SQL logging level. + - Options: `debug`, `error`, `warn`, `info`. + - Default: `info`. +- **`LogZap`**: Enable Zap logging for SQL queries. + - Default: `true`. +- **`SlowThreshold`**: Threshold for slow query logging (in milliseconds). + - Default: `1000`. - - Password: Admin login password, default: password +### 3.5 Redis (`Redis`) -#### 4. Environment Variables +- **`Host`**: Redis server address. + - Default: `localhost:6379`. +- **`Pass`**: Redis password. + - Default: `""` (no password). +- **`DB`**: Redis database index. + - Default: `0`. -Supported environment variables are as follows: +### 3.6 Admin Login (`Administer`) -| Environment Variable | Configuration | Example | -|-----------------------|---------------|:-------------------------------------------| -| PPANEL_DB | MySQL config | root:password@tcp(localhost:3306)/vpnboard | -| PPANEL_REDIS | Redis config | redis://localhost:6379" | +- **`Email`**: Admin login email. + - Default: `admin@ppanel.dev`. +- **`Password`**: Admin login password. + - Default: `password`. + +## 4. Environment Variables + +The following environment variables can be used to override configuration settings: + +| Environment Variable | Configuration Section | Example Value | +|----------------------|-----------------------|----------------------------------------------| +| `PPANEL_DB` | MySQL | `root:password@tcp(localhost:3306)/vpnboard` | +| `PPANEL_REDIS` | Redis | `redis://localhost:6379` | + +## 5. Best Practices + +- **Security**: Avoid using default `Administer` credentials in production. Update `Email` and `Password` to secure + values. +- **Logging**: Use `file` or `volume` mode for production to persist logs. Adjust `Level` to `error` or `severe` to + reduce log volume. +- **Database**: Ensure `MySQL` and `Redis` credentials are secure and not exposed in version control. +- **JWT**: Specify a strong `AccessSecret` for `JwtAuth` to enhance security. + +For further assistance, refer to the official PPanel documentation or contact support. \ No newline at end of file diff --git a/doc/image/architecture-en.png b/doc/image/architecture-en.png new file mode 100644 index 0000000..978774c Binary files /dev/null and b/doc/image/architecture-en.png differ diff --git a/doc/image/architecture-zh.png b/doc/image/architecture-zh.png new file mode 100644 index 0000000..7923f60 Binary files /dev/null and b/doc/image/architecture-zh.png differ diff --git a/doc/install-zh.md b/doc/install-zh.md index 7410580..10d8292 100644 --- a/doc/install-zh.md +++ b/doc/install-zh.md @@ -6,14 +6,14 @@ #### 二进制安装 1.确定系统架构,并下载对应的二进制文件 -下载地址:`https://github.com/perfect-panel/ppanel/releases` +下载地址:`https://github.com/perfect-panel/server/releases` 示例说明:系统:Linux amd64,用户:root,当前目录:/root - 下载二进制文件 ```shell -$ wget https://github.com/perfect-panel/ppanel/releases/download/v0.1.0/ppanel-server-linux-amd64.tar.gz +$ wget https://github.com/perfect-panel/server/releases/download/v1.0.0/ppanel-server-linux-amd64.tar.gz ``` - 解压二进制文件 diff --git a/doc/install.md b/doc/install.md index e5ab48c..bcecce9 100644 --- a/doc/install.md +++ b/doc/install.md @@ -8,14 +8,14 @@ 1. Determine your system architecture and download the corresponding binary file. -Download URL: `https://github.com/perfect-panel/ppanel/releases` +Download URL: `https://github.com/perfect-panel/server/releases` Example setup: OS: Linux amd64, User: root, Current directory: `/root` - Download the binary file: ```shell -$ wget https://github.com/perfect-panel/ppanel/releases/download/v0.1.0/ppanel-server-linux-amd64.tar.gz +$ wget https://github.com/perfect-panel/server/releases/download/v1.0.0/ppanel-server-linux-amd64.tar.gz ``` - Extract the binary file: diff --git a/generate/gopure-amd64.exe b/generate/gopure-amd64.exe old mode 100644 new mode 100755 diff --git a/generate/gopure-arm64.exe b/generate/gopure-arm64.exe old mode 100644 new mode 100755 diff --git a/generate/gopure-darwin-amd64 b/generate/gopure-darwin-amd64 old mode 100644 new mode 100755 diff --git a/generate/gopure-darwin-arm64 b/generate/gopure-darwin-arm64 old mode 100644 new mode 100755 diff --git a/generate/gopure-linux-amd64 b/generate/gopure-linux-amd64 old mode 100644 new mode 100755 diff --git a/generate/gopure-linux-arm64 b/generate/gopure-linux-arm64 old mode 100644 new mode 100755 diff --git a/go.mod b/go.mod index 5f32d38..04daadd 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/perfect-panel/ppanel-server +module github.com/perfect-panel/server go 1.23.3 @@ -20,7 +20,7 @@ require ( github.com/go-sql-driver/mysql v1.8.1 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 github.com/gofrs/uuid/v5 v5.3.0 - github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 github.com/hibiken/asynq v0.24.1 @@ -28,21 +28,21 @@ require ( github.com/klauspost/compress v1.17.7 github.com/nyaruka/phonenumbers v1.5.0 github.com/pkg/errors v0.9.1 - github.com/redis/go-redis/v9 v9.6.1 + github.com/redis/go-redis/v9 v9.7.2 github.com/smartwalle/alipay/v3 v3.2.23 github.com/spf13/cast v1.7.0 // indirect github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.10.0 github.com/stripe/stripe-go/v81 v81.1.0 github.com/twilio/twilio-go v1.23.11 - go.opentelemetry.io/otel v1.24.0 + go.opentelemetry.io/otel v1.29.0 go.opentelemetry.io/otel/exporters/jaeger v1.17.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 go.opentelemetry.io/otel/exporters/zipkin v1.24.0 - go.opentelemetry.io/otel/sdk v1.24.0 - go.opentelemetry.io/otel/trace v1.24.0 + go.opentelemetry.io/otel/sdk v1.29.0 + go.opentelemetry.io/otel/trace v1.29.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.32.0 golang.org/x/oauth2 v0.25.0 @@ -56,16 +56,21 @@ require ( ) require ( + github.com/Masterminds/sprig/v3 v3.3.0 github.com/fatih/color v1.18.0 github.com/goccy/go-json v0.10.4 + github.com/golang-migrate/migrate/v4 v4.18.2 github.com/spaolacci/murmur3 v1.1.0 - google.golang.org/grpc v1.61.1 + google.golang.org/grpc v1.64.1 google.golang.org/protobuf v1.36.3 ) require ( cloud.google.com/go/compute/metadata v0.6.0 // indirect + dario.cat/mergo v1.0.1 // indirect filippo.io/edwards25519 v1.1.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect github.com/alibabacloud-go/debug v1.0.1 // indirect github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect @@ -88,14 +93,17 @@ require ( github.com/gin-contrib/sse v1.0.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang/glog v1.1.2 // indirect + github.com/golang/glog v1.2.0 // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/gomodule/redigo v2.0.0+incompatible // indirect github.com/gorilla/context v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/sessions v1.2.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect @@ -104,12 +112,15 @@ require ( github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/openzipkin/zipkin-go v0.4.2 // 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/robfig/cron/v3 v3.0.1 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/smartwalle/ncrypto v1.0.4 // indirect github.com/smartwalle/ngx v1.0.9 // indirect github.com/smartwalle/nsign v1.0.9 // indirect @@ -119,17 +130,18 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/yuin/gopher-lua v1.1.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/proto/otlp v1.1.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.13.0 // indirect golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // 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 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index 841511b..9e9f2c5 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,23 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f h1:RDkg3pyE1qGbBpRWmvSN9RNZC5nUrOaEPiEpEb8y2f0= github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f/go.mod h1:zA7AF9RTfpluCfz0omI4t5KCMaWHUMicsZoMccnaT44= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g= @@ -87,11 +99,23 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dhui/dktest v0.4.4 h1:+I4s6JRE1yGuqflzwqG+aIaMdgXIorCf5P98JnaAWa8= +github.com/dhui/dktest v0.4.4/go.mod h1:4+22R4lgsdAXrDyaH4Nqx2JEz2hLp49MqQmm9HLCQhM= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= +github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/forgoer/openssl v1.6.0 h1:IueL+UfH0hKo99xFPojHLlO3QzRBQqFY+Cht0WwtOC0= github.com/forgoer/openssl v1.6.0/go.mod h1:9DZ4yOsQmveP0aXC/BpQ++Y5TKaz5yR9+emcxmIZNZs= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -128,12 +152,16 @@ github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8= +github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= @@ -179,10 +207,17 @@ github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTj github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hibiken/asynq v0.24.1 h1:+5iIEAyA9K/lcSPvx3qoPtsKJeKI5u9aOIvUmSsazEw= github.com/hibiken/asynq v0.24.1/go.mod h1:u5qVeSbrnfT+vtG5Mq8ZPzQu/BmCKMHvTGb91uy9Tts= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= @@ -212,6 +247,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275 h1:IZycmTpoUtQK3PD60UYBwjaCUHUP7cML494ao9/O8+Q= github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275/go.mod h1:zt6UU74K6Z6oMOYJbJzYpYucqdcQwSMPBEdSvGiaUMw= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -220,8 +257,16 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= -github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= -github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -229,9 +274,15 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nyaruka/phonenumbers v1.5.0 h1:0M+Gd9zl53QC4Nl5z1Yj1O/zPk2XXBUwR/vlzdXSJv4= github.com/nyaruka/phonenumbers v1.5.0/go.mod h1:gv+CtldaFz+G3vHHnasBSirAi3O2XLqZzVWz4V1pl2E= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +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/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= @@ -243,13 +294,15 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= -github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= -github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= +github.com/redis/go-redis/v9 v9.7.2 h1:PSGhv13dJyrTCw1+55H0pIKM3WFov7HuUrKUmInGL0o= +github.com/redis/go-redis/v9 v9.7.2/go.mod h1:yp5+a5FnEEP0/zTYuw6u6/2nn3zivwhv274qYgWQhDM= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/smartwalle/alipay/v3 v3.2.23 h1:i1VwJeu70EmwpsXXz6GZZnMAtRx5MTfn2dPoql/L3zE= github.com/smartwalle/alipay/v3 v3.2.23/go.mod h1:lVqFiupPf8YsAXaq5JXcwqnOUC2MCF+2/5vub+RlagE= github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8= @@ -306,12 +359,14 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= @@ -320,14 +375,16 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDO go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA= go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY= go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= -go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -446,18 +503,16 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= -google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= -google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= -google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= +google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 h1:W5Xj/70xIA4x60O/IFyXivR5MGqblAb8R3w26pnD6No= +google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= -google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/initialize/config.go b/initialize/config.go index 1df7ec1..025220f 100644 --- a/initialize/config.go +++ b/initialize/config.go @@ -9,16 +9,16 @@ import ( "net/http" "os" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" "gorm.io/driver/mysql" "github.com/gin-gonic/gin" "github.com/google/uuid" - "github.com/perfect-panel/ppanel-server/initialize/migrate" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/pkg/conf" - "github.com/perfect-panel/ppanel-server/pkg/orm" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/initialize/migrate" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/pkg/conf" + "github.com/perfect-panel/server/pkg/orm" + "github.com/perfect-panel/server/pkg/tool" "github.com/pkg/errors" "gopkg.in/yaml.v3" "gorm.io/gorm" @@ -141,12 +141,25 @@ func handleInitConfig(c *gin.Context) { c.Abort() return } - - // init - if err := initMysql(db, request.AdminEmail, request.AdminPassword); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ + dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", request.MysqlUser, request.MysqlPassword, request.MysqlHost, request.MysqlPort, request.MysqlDatabase) + // migrate database + if err = migrate.Migrate(dsn).Up(); err != nil { + logger.Errorf("[Init Mysql] Migrate failed: %v", err.Error()) + c.JSON(http.StatusOK, gin.H{ "code": 500, - "msg": "MySQL initialization failed", + "msg": "Database migration failed", + "data": nil, + }) + c.Abort() + return + } + + // create admin user + if err = migrate.CreateAdminUser(request.AdminEmail, request.AdminPassword, db); err != nil { + logger.Errorf("[Init Mysql] Create admin user failed: %v", err.Error()) + c.JSON(http.StatusOK, gin.H{ + "code": 500, + "msg": "Admin user creation failed", "data": nil, }) c.Abort() @@ -154,7 +167,7 @@ func handleInitConfig(c *gin.Context) { } // write to file - if err := os.WriteFile(configPath, fileData, 0644); err != nil { + if err = os.WriteFile(configPath, fileData, 0644); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "code": 500, "msg": "Configuration initialization failed", @@ -258,20 +271,3 @@ func HandleRedisTest(c *gin.Context) { "status": true, }) } - -func initMysql(tx *gorm.DB, email, password string) error { - tables, err := tx.Migrator().GetTables() - if err != nil { - return fmt.Errorf("database table validation failed: %w", err) - } - if len(tables) > 0 { - return errors.New("the database contains existing data. Please clear it before proceeding with the installation") - } - if err := migrate.InitPPanelSQL(tx); err != nil { - return fmt.Errorf("failed to initialize database: %w", err) - } - if err := migrate.CreateAdminUser(email, password, tx); err != nil { - return fmt.Errorf("failed to create admin user: %w", err) - } - return nil -} diff --git a/initialize/device.go b/initialize/device.go new file mode 100644 index 0000000..1b8c527 --- /dev/null +++ b/initialize/device.go @@ -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 +} diff --git a/initialize/email.go b/initialize/email.go index b740cca..f57d30a 100644 --- a/initialize/email.go +++ b/initialize/email.go @@ -5,12 +5,11 @@ import ( "encoding/json" "fmt" - "github.com/perfect-panel/ppanel-server/pkg/logger" - - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "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/logger" + "github.com/perfect-panel/server/pkg/tool" ) // Email get email smtp config @@ -18,13 +17,11 @@ func Email(ctx *svc.ServiceContext) { logger.Debug("Email config initialization") method, err := ctx.AuthModel.FindOneByMethod(context.Background(), "email") if err != nil { - panic(fmt.Sprintf("failed to find email auth method: %v", err.Error())) + panic(fmt.Sprintf("[Error] Initialization Failed to find email auth method: %v", err.Error())) } var cfg config.EmailConfig var emailConfig = new(auth.EmailAuthConfig) - if err := emailConfig.Unmarshal(method.Config); err != nil { - panic(fmt.Sprintf("failed to unmarshal email auth config: %v", err.Error())) - } + emailConfig.Unmarshal(method.Config) tool.DeepCopy(&cfg, emailConfig) cfg.Enable = *method.Enabled value, _ := json.Marshal(emailConfig.PlatformConfig) diff --git a/initialize/init.go b/initialize/init.go index 00453d5..8023ce5 100644 --- a/initialize/init.go +++ b/initialize/init.go @@ -1,20 +1,20 @@ package initialize -import "github.com/perfect-panel/ppanel-server/internal/svc" +import ( + "github.com/perfect-panel/server/internal/svc" +) func StartInitSystemConfig(svc *svc.ServiceContext) { - // Initialize the system configuration - Mysql(svc) - VerifyVersion(svc) + Migrate(svc) Site(svc) Node(svc) Email(svc) + Device(svc) Invite(svc) Verify(svc) Subscribe(svc) Register(svc) Mobile(svc) - TrafficDataToRedis(svc) if !svc.Config.Debug { Telegram(svc) } diff --git a/initialize/invite.go b/initialize/invite.go index c696097..9375417 100644 --- a/initialize/invite.go +++ b/initialize/invite.go @@ -3,10 +3,10 @@ package initialize import ( "context" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/tool" ) func Invite(ctx *svc.ServiceContext) { diff --git a/initialize/migrate/database/00001_init_schema.down.sql b/initialize/migrate/database/00001_init_schema.down.sql new file mode 100644 index 0000000..4b8470b --- /dev/null +++ b/initialize/migrate/database/00001_init_schema.down.sql @@ -0,0 +1,36 @@ +-- 000001_init_schema.down.sql +SET FOREIGN_KEY_CHECKS = 0; + +DROP TABLE IF EXISTS `user_subscribe_log`; +DROP TABLE IF EXISTS `user_subscribe`; +DROP TABLE IF EXISTS `user_login_log`; +DROP TABLE IF EXISTS `user_gift_amount_log`; +DROP TABLE IF EXISTS `user_device`; +DROP TABLE IF EXISTS `user_commission_log`; +DROP TABLE IF EXISTS `user_balance_log`; +DROP TABLE IF EXISTS `user_auth_methods`; +DROP TABLE IF EXISTS `user`; +DROP TABLE IF EXISTS `traffic_log`; +DROP TABLE IF EXISTS `ticket_follow`; +DROP TABLE IF EXISTS `ticket`; +DROP TABLE IF EXISTS `system`; +DROP TABLE IF EXISTS `subscribe_type`; +DROP TABLE IF EXISTS `subscribe_group`; +DROP TABLE IF EXISTS `subscribe`; +DROP TABLE IF EXISTS `sms`; +DROP TABLE IF EXISTS `server_rule_group`; +DROP TABLE IF EXISTS `server_group`; +DROP TABLE IF EXISTS `server`; +DROP TABLE IF EXISTS `payment`; +DROP TABLE IF EXISTS `order`; +DROP TABLE IF EXISTS `message_log`; +DROP TABLE IF EXISTS `document`; +DROP TABLE IF EXISTS `coupon`; +DROP TABLE IF EXISTS `auth_method`; +DROP TABLE IF EXISTS `application_version`; +DROP TABLE IF EXISTS `application_config`; +DROP TABLE IF EXISTS `application`; +DROP TABLE IF EXISTS `announcement`; +DROP TABLE IF EXISTS `ads`; + +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/initialize/migrate/database/00001_init_schema.up.sql b/initialize/migrate/database/00001_init_schema.up.sql new file mode 100644 index 0000000..9be1ddb --- /dev/null +++ b/initialize/migrate/database/00001_init_schema.up.sql @@ -0,0 +1,555 @@ +-- 000001_init_schema.up.sql +SET FOREIGN_KEY_CHECKS = 0; + +CREATE TABLE IF NOT EXISTS `ads` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Ads title', + `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Ads type', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Ads content', + `target_url` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Ads target url', + `start_time` datetime DEFAULT NULL COMMENT 'Ads start time', + `end_time` datetime DEFAULT NULL COMMENT 'Ads end time', + `status` tinyint(1) DEFAULT '0' COMMENT 'Ads status,0 disable,1 enable', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `announcement` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Title', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Content', + `show` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Show', + `pinned` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Pinned', + `popup` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Popup', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `application` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用名称', + `icon` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '应用图标', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '更新描述', + `subscribe_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订阅类型', + `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间', + `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `application_config` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `app_id` bigint NOT NULL DEFAULT '0' COMMENT 'App id', + `encryption_key` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Encryption Key', + `encryption_method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Encryption Method', + `domains` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, + `startup_picture` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, + `startup_picture_skip_time` bigint NOT NULL DEFAULT '0' COMMENT 'Startup Picture Skip Time', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `application_version` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用地址', + `version` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用版本', + `platform` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用平台', + `is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT '默认版本', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '更新描述', + `application_id` bigint DEFAULT NULL COMMENT '所属应用', + `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间', + `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `fk_application_application_versions` (`application_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `auth_method` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'method', + `config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'OAuth Configuration', + `enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Enabled', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`), + UNIQUE KEY `uni_auth_method` (`method`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `coupon` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Coupon Name', + `code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Coupon Code', + `count` bigint NOT NULL DEFAULT '0' COMMENT 'Count Limit', + `type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Coupon Type: 1: Percentage 2: Fixed Amount', + `discount` bigint NOT NULL DEFAULT '0' COMMENT 'Coupon Discount', + `start_time` bigint NOT NULL DEFAULT '0' COMMENT 'Start Time', + `expire_time` bigint NOT NULL DEFAULT '0' COMMENT 'Expire Time', + `user_limit` bigint NOT NULL DEFAULT '0' COMMENT 'User Limit', + `subscribe` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subscribe Limit', + `used_count` bigint NOT NULL DEFAULT '0' COMMENT 'Used Count', + `enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Enable', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`), + UNIQUE KEY `uni_coupon_code` (`code`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `document` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Document Title', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Document Content', + `tags` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Document Tags', + `show` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Show', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `message_log` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'email' COMMENT 'Message Type', + `platform` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'smtp' COMMENT 'Platform', + `to` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'To', + `subject` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subject', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Content', + `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Status', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `order` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `parent_id` bigint DEFAULT NULL COMMENT 'Parent Order Id', + `user_id` bigint NOT NULL DEFAULT '0' COMMENT 'User Id', + `order_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Order No', + `type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Order Type: 1: Subscribe, 2: Renewal, 3: ResetTraffic, 4: Recharge', + `quantity` bigint NOT NULL DEFAULT '1' COMMENT 'Quantity', + `price` bigint NOT NULL DEFAULT '0' COMMENT 'Original price', + `amount` bigint NOT NULL DEFAULT '0' COMMENT 'Order Amount', + `gift_amount` bigint NOT NULL DEFAULT '0' COMMENT 'User Gift Amount', + `discount` bigint NOT NULL DEFAULT '0' COMMENT 'Discount Amount', + `coupon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Coupon', + `coupon_discount` bigint NOT NULL DEFAULT '0' COMMENT 'Coupon Discount Amount', + `commission` bigint NOT NULL DEFAULT '0' COMMENT 'Order Commission', + `payment_id` bigint NOT NULL DEFAULT '-1' COMMENT 'Payment Id', + `method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Payment Method', + `fee_amount` bigint NOT NULL DEFAULT '0' COMMENT 'Fee Amount', + `trade_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Trade No', + `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Order Status: 1: Pending, 2: Paid, 3:Close, 4: Failed, 5:Finished', + `subscribe_id` bigint NOT NULL DEFAULT '0' COMMENT 'Subscribe Id', + `subscribe_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Renewal Subscribe Token', + `is_new` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is New Order', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`), + UNIQUE KEY `uni_order_order_no` (`order_no`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `payment` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Payment Name', + `platform` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Payment Platform', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Payment Description', + `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Payment Icon', + `domain` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Notification Domain', + `config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Payment Configuration', + `fee_mode` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Fee Mode: 0: No Fee 1: Percentage 2: Fixed Amount 3: Percentage + Fixed Amount', + `fee_percent` bigint DEFAULT '0' COMMENT 'Fee Percentage', + `fee_amount` bigint DEFAULT '0' COMMENT 'Fixed Fee Amount', + `enable` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Enabled', + `token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Payment Token', + PRIMARY KEY (`id`), + UNIQUE KEY `uni_payment_token` (`token`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `server` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Node Name', + `tags` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Tags', + `country` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Country', + `city` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'City', + `latitude` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'latitude', + `longitude` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'longitude', + `server_addr` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Server Address', + `relay_mode` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'none' COMMENT 'Relay Mode', + `relay_node` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Relay Node', + `speed_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Speed Limit', + `traffic_ratio` decimal(4, 2) NOT NULL DEFAULT '0.00' COMMENT 'Traffic Ratio', + `group_id` bigint DEFAULT NULL COMMENT 'Group ID', + `protocol` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Protocol', + `config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Config', + `enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Enabled', + `sort` bigint NOT NULL DEFAULT '0' COMMENT 'Sort', + `last_reported_at` datetime(3) DEFAULT NULL COMMENT 'Last Reported Time', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`), + KEY `idx_group_id` (`group_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `server_group` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Group Name', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Group Description', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +-- if `sms` not exist, create it +CREATE TABLE IF NOT EXISTS `sms` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, + `platform` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `area_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `telephone` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `status` tinyint(1) DEFAULT '1', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `subscribe` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subscribe Name', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Subscribe Description', + `unit_price` bigint NOT NULL DEFAULT '0' COMMENT 'Unit Price', + `unit_time` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Unit Time', + `discount` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Discount', + `replacement` bigint NOT NULL DEFAULT '0' COMMENT 'Replacement', + `inventory` bigint NOT NULL DEFAULT '0' COMMENT 'Inventory', + `traffic` bigint NOT NULL DEFAULT '0' COMMENT 'Traffic', + `speed_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Speed Limit', + `device_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Device Limit', + `quota` bigint NOT NULL DEFAULT '0' COMMENT 'Quota', + `group_id` bigint DEFAULT NULL COMMENT 'Group Id', + `server_group` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Server Group', + `server` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Server', + `show` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Show portal page', + `sell` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Sell', + `sort` bigint NOT NULL DEFAULT '0' COMMENT 'Sort', + `deduction_ratio` bigint DEFAULT '0' COMMENT 'Deduction Ratio', + `allow_deduction` tinyint(1) DEFAULT '1' COMMENT 'Allow deduction', + `reset_cycle` bigint DEFAULT '0' COMMENT 'Reset Cycle: 0: No Reset, 1: 1st, 2: Monthly, 3: Yearly', + `renewal_reset` tinyint(1) DEFAULT '0' COMMENT 'Renew Reset', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `subscribe_group` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Group Name', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Group Description', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `subscribe_type` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订阅类型', + `mark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订阅标识', + `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间', + `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `system` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `category` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Category', + `key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Key Name', + `value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Key Value', + `type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Type', + `desc` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Description', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`), + UNIQUE KEY `uni_system_key` (`key`), + KEY `index_key` (`key`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `ticket` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Title', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Description', + `user_id` bigint NOT NULL DEFAULT '0' COMMENT 'UserId', + `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Status', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `ticket_follow` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `ticket_id` bigint NOT NULL DEFAULT '0' COMMENT 'TicketId', + `from` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'From', + `type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Type: 1 text, 2 image', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Content', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `traffic_log` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `server_id` bigint NOT NULL COMMENT 'Server ID', + `user_id` bigint NOT NULL COMMENT 'User ID', + `subscribe_id` bigint NOT NULL COMMENT 'Subscription ID', + `download` bigint DEFAULT '0' COMMENT 'Download Traffic', + `upload` bigint DEFAULT '0' COMMENT 'Upload Traffic', + `timestamp` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Traffic Log Time', + PRIMARY KEY (`id`), + KEY `idx_subscribe_id` (`subscribe_id`), + KEY `idx_server_id` (`server_id`), + KEY `idx_user_id` (`user_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `user` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'User Password', + `avatar` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'User Avatar', + `balance` bigint DEFAULT '0' COMMENT 'User Balance', + `telegram` bigint DEFAULT NULL COMMENT 'Telegram Account', + `refer_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Referral Code', + `referer_id` bigint DEFAULT NULL COMMENT 'Referrer ID', + `commission` bigint DEFAULT '0' COMMENT 'Commission', + `gift_amount` bigint DEFAULT '0' COMMENT 'User Gift Amount', + `enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Is Account Enabled', + `is_admin` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Admin', + `valid_email` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Email Verified', + `enable_email_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Email Notifications', + `enable_telegram_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Telegram Notifications', + `enable_balance_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Balance Change Notifications', + `enable_login_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Login Notifications', + `enable_subscribe_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Subscription Notifications', + `enable_trade_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Trade Notifications', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + `deleted_at` datetime(3) DEFAULT NULL COMMENT 'Deletion Time', + `is_del` bigint unsigned DEFAULT NULL COMMENT '1: Normal 0: Deleted', + PRIMARY KEY (`id`), + KEY `idx_referer` (`referer_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `user_auth_methods` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL COMMENT 'User ID', + `auth_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Auth Type 1: apple 2: google 3: github 4: facebook 5: telegram 6: email 7: phone', + `auth_identifier` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Auth Identifier', + `verified` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Verified', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`), + UNIQUE KEY `idx_auth_identifier` (`auth_identifier`), + KEY `idx_user_id` (`user_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `user_balance_log` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL COMMENT 'User ID', + `amount` bigint NOT NULL COMMENT 'Amount', + `type` tinyint(1) NOT NULL COMMENT 'Type: 1: Recharge 2: Withdraw 3: Payment 4: Refund 5: Reward', + `order_id` bigint DEFAULT NULL COMMENT 'Order ID', + `balance` bigint NOT NULL COMMENT 'Balance', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `user_commission_log` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL COMMENT 'User ID', + `order_no` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Order No.', + `amount` bigint NOT NULL COMMENT 'Amount', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `user_device` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL COMMENT 'User ID', + `subscribe_id` bigint DEFAULT NULL COMMENT 'Subscribe ID', + `ip` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Ip.', + `Identifier` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Identifier.', + `user_agent` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device User Agent.', + `online` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Online', + `enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'EnableDeviceNumber', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `user_gift_amount_log` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL COMMENT 'User ID', + `user_subscribe_id` bigint DEFAULT NULL COMMENT 'Deduction User Subscribe ID', + `order_no` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Order No.', + `type` tinyint(1) NOT NULL COMMENT 'Type: 1: Increase 2: Reduce', + `amount` bigint NOT NULL COMMENT 'Amount', + `balance` bigint NOT NULL COMMENT 'Balance', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Remark', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `user_login_log` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL COMMENT 'User ID', + `login_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Login IP', + `user_agent` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'UserAgent', + `success` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Login Success', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `user_subscribe` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL COMMENT 'User ID', + `order_id` bigint NOT NULL COMMENT 'Order ID', + `subscribe_id` bigint NOT NULL COMMENT 'Subscription ID', + `start_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Subscription Start Time', + `expire_time` datetime(3) DEFAULT NULL COMMENT 'Subscription Expire Time', + `traffic` bigint DEFAULT '0' COMMENT 'Traffic', + `download` bigint DEFAULT '0' COMMENT 'Download Traffic', + `upload` bigint DEFAULT '0' COMMENT 'Upload Traffic', + `token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Token', + `uuid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'UUID', + `status` tinyint(1) DEFAULT '0' COMMENT 'Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired 4: Deducted', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`), + UNIQUE KEY `uni_user_subscribe_token` (`token`), + UNIQUE KEY `uni_user_subscribe_uuid` (`uuid`), + KEY `idx_user_id` (`user_id`), + KEY `idx_order_id` (`order_id`), + KEY `idx_subscribe_id` (`subscribe_id`), + KEY `idx_token` (`token`), + KEY `idx_uuid` (`uuid`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `user_subscribe_log` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL COMMENT 'User ID', + `user_subscribe_id` bigint NOT NULL COMMENT 'User Subscribe ID', + `token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Token', + `ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'IP', + `user_agent` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'UserAgent', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_user_subscribe_id` (`user_subscribe_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `server_rule_group` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Rule Group Name', + `icon` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Rule Group Icon', + `tags` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Selected Node Tags', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Rule Group Description', + `enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Rule Group Enable', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`), + UNIQUE KEY `unique_name` (`name`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/initialize/migrate/database/00002_init_basic_data.down.sql b/initialize/migrate/database/00002_init_basic_data.down.sql new file mode 100644 index 0000000..7d749b2 --- /dev/null +++ b/initialize/migrate/database/00002_init_basic_data.down.sql @@ -0,0 +1,21 @@ +-- 000002_init_data.down.sql +SET +FOREIGN_KEY_CHECKS = 0; + +DELETE +FROM `auth_method` +WHERE `id` IN (1, 2, 3, 4, 5, 6, 7, 8); +DELETE +FROM `payment` +WHERE `id` = -1; +DELETE +FROM `subscribe_type` +WHERE `id` IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14); +DELETE +FROM `system` +WHERE `id` IN + (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41); + +SET +FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/initialize/migrate/database/00002_init_basic_data.up.sql b/initialize/migrate/database/00002_init_basic_data.up.sql new file mode 100644 index 0000000..83cdda6 --- /dev/null +++ b/initialize/migrate/database/00002_init_basic_data.up.sql @@ -0,0 +1,127 @@ +-- 000002_init_data.up.sql +SET FOREIGN_KEY_CHECKS = 0; + +-- auth_method +INSERT IGNORE INTO `auth_method` (`id`, `method`, `config`, `enabled`, `created_at`, `updated_at`) +VALUES (1, 'email', + '{"platform":"smtp","platform_config":{"host":"","port":0,"user":"","pass":"","from":"","ssl":false},"enable_verify":false,"enable_notify":false,"enable_domain_suffix":false,"domain_suffix_list":"","verify_email_template":"","expiration_email_template":"","maintenance_email_template":"","traffic_exceed_email_template":""}', + 1, '2025-04-22 14:25:16.642', '2025-04-22 14:25:16.642'), + (2, 'mobile', + '{"platform":"AlibabaCloud","platform_config":{"access":"","secret":"","sign_name":"","endpoint":"","template_code":""},"enable_whitelist":false,"whitelist":[]}', + 0, '2025-04-22 14:25:16.642', '2025-04-22 14:25:16.642'), + (3, 'apple', '{"team_id":"","key_id":"","client_id":"","client_secret":"","redirect_url":""}', 0, + '2025-04-22 14:25:16.642', '2025-04-22 14:25:16.642'), + (4, 'google', '{"client_id":"","client_secret":"","redirect_url":""}', 0, '2025-04-22 14:25:16.642', + '2025-04-22 14:25:16.642'), + (5, 'github', '{"client_id":"","client_secret":"","redirect_url":""}', 0, '2025-04-22 14:25:16.642', + '2025-04-22 14:25:16.642'), + (6, 'facebook', '{"client_id":"","client_secret":"","redirect_url":""}', 0, '2025-04-22 14:25:16.642', + '2025-04-22 14:25:16.642'), + (7, 'telegram', '{"bot_token":"","enable_notify":false,"webhook_domain":""}', 0, '2025-04-22 14:25:16.642', + '2025-04-22 14:25:16.642'), + (8, 'device', '{"show_ads":false,"only_real_device":false,"enable_security":false,"security_secret":""}', 0, + '2025-04-22 14:25:16.642', '2025-04-22 14:25:16.642'); + +-- payment +INSERT IGNORE INTO `payment` (`id`, `name`, `platform`, `description`, `icon`, `domain`, `config`, `fee_mode`, + `fee_percent`, `fee_amount`, `enable`, `token`) +VALUES (-1, 'Balance', 'balance', '', '', '', '', 0, 0, 0, 1, ''); + +-- subscribe_type +INSERT IGNORE INTO `subscribe_type` (`id`, `name`, `mark`, `created_at`, `updated_at`) +VALUES (1, 'Clash', 'Clash', '2025-04-22 14:25:16.648', '2025-04-22 14:25:16.648'), + (2, 'Hiddify', 'Hiddify', '2025-04-22 14:25:16.648', '2025-04-22 14:25:16.648'), + (3, 'Loon', 'Loon', '2025-04-22 14:25:16.648', '2025-04-22 14:25:16.648'), + (4, 'NekoBox', 'NekoBox', '2025-04-22 14:25:16.648', '2025-04-22 14:25:16.648'), + (5, 'NekoRay', 'NekoRay', '2025-04-22 14:25:16.648', '2025-04-22 14:25:16.648'), + (6, 'Netch', 'Netch', '2025-04-22 14:25:16.648', '2025-04-22 14:25:16.648'), + (7, 'Quantumult', 'Quantumult', '2025-04-22 14:25:16.648', '2025-04-22 14:25:16.648'), + (8, 'Shadowrocket', 'Shadowrocket', '2025-04-22 14:25:16.648', '2025-04-22 14:25:16.648'), + (9, 'SingBox', ' SingBox', '2025-04-22 14:25:16.648', '2025-04-22 14:25:16.648'), + (10, 'Surfboard', 'Surfboard', '2025-04-22 14:25:16.648', '2025-04-22 14:25:16.648'), + (11, 'Surge', 'Surge', '2025-04-22 14:25:16.648', '2025-04-22 14:25:16.648'), + (12, 'V2box', 'V2box', '2025-04-22 14:25:16.648', '2025-04-22 14:25:16.648'), + (13, 'V2rayN', 'V2rayN', '2025-04-22 14:25:16.648', '2025-04-22 14:25:16.648'), + (14, 'V2rayNg', 'V2rayNg', '2025-04-22 14:25:16.648', '2025-04-22 14:25:16.648'); + +-- system +INSERT IGNORE INTO `system` (`id`, `category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`) +VALUES (1, 'site', 'SiteLogo', '/favicon.svg', 'string', 'Site Logo', '2025-04-22 14:25:16.637', + '2025-04-22 14:25:16.637'), + (2, 'site', 'SiteName', 'Perfect Panel', 'string', 'Site Name', '2025-04-22 14:25:16.637', + '2025-04-22 14:25:16.637'), + (3, 'site', 'SiteDesc', + 'PPanel is a pure, professional, and perfect open-source proxy panel tool, designed to be your ideal choice for learning and practical use.', + 'string', 'Site Description', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637'), + (4, 'site', 'Host', '', 'string', 'Site Host', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637'), + (5, 'site', 'Keywords', 'Perfect Panel,PPanel', 'string', 'Site Keywords', '2025-04-22 14:25:16.637', + '2025-04-22 14:25:16.637'), + (6, 'site', 'CustomHTML', '', 'string', 'Custom HTML', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637'), + (7, 'tos', 'TosContent', 'Welcome to use Perfect Panel', 'string', 'Terms of Service', '2025-04-22 14:25:16.637', + '2025-04-22 14:25:16.637'), + (8, 'tos', 'PrivacyPolicy', '', 'string', 'PrivacyPolicy', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637'), + (9, 'ad', 'WebAD', 'false', 'bool', 'Display ad on the web', '2025-04-22 14:25:16.637', + '2025-04-22 14:25:16.637'), + (10, 'subscribe', 'SingleModel', 'false', 'bool', '是否单订阅模式', '2025-04-22 14:25:16.639', + '2025-04-22 14:25:16.639'), + (11, 'subscribe', 'SubscribePath', '/api/subscribe', 'string', '订阅路径', '2025-04-22 14:25:16.639', + '2025-04-22 14:25:16.639'), + (12, 'subscribe', 'SubscribeDomain', '', 'string', '订阅域名', '2025-04-22 14:25:16.639', + '2025-04-22 14:25:16.639'), + (13, 'subscribe', 'PanDomain', 'false', 'bool', '是否使用泛域名', '2025-04-22 14:25:16.639', + '2025-04-22 14:25:16.639'), + (14, 'verify', 'TurnstileSiteKey', '', 'string', 'TurnstileSiteKey', '2025-04-22 14:25:16.639', + '2025-04-22 14:25:16.639'), + (15, 'verify', 'TurnstileSecret', '', 'string', 'TurnstileSecret', '2025-04-22 14:25:16.639', + '2025-04-22 14:25:16.639'), + (16, 'verify', 'EnableLoginVerify', 'false', 'bool', 'is enable login verify', '2025-04-22 14:25:16.639', + '2025-04-22 14:25:16.639'), + (17, 'verify', 'EnableRegisterVerify', 'false', 'bool', 'is enable register verify', '2025-04-22 14:25:16.639', + '2025-04-22 14:25:16.639'), + (18, 'verify', 'EnableResetPasswordVerify', 'false', 'bool', 'is enable reset password verify', + '2025-04-22 14:25:16.639', '2025-04-22 14:25:16.639'), + (19, 'server', 'NodeSecret', '12345678', 'string', 'node secret', '2025-04-22 14:25:16.640', + '2025-04-22 14:25:16.640'), + (20, 'server', 'NodePullInterval', '10', 'int', 'node pull interval', '2025-04-22 14:25:16.640', + '2025-04-22 14:25:16.640'), + (21, 'server', 'NodePushInterval', '60', 'int', 'node push interval', '2025-04-22 14:25:16.640', + '2025-04-22 14:25:16.640'), + (22, 'server', 'NodeMultiplierConfig', '[]', 'string', 'node multiplier config', '2025-04-22 14:25:16.640', + '2025-04-22 14:25:16.640'), + (23, 'invite', 'ForcedInvite', 'false', 'bool', 'Forced invite', '2025-04-22 14:25:16.640', + '2025-04-22 14:25:16.640'), + (24, 'invite', 'ReferralPercentage', '20', 'int', 'Referral percentage', '2025-04-22 14:25:16.640', + '2025-04-22 14:25:16.640'), + (25, 'invite', 'OnlyFirstPurchase', 'false', 'bool', 'Only first purchase', '2025-04-22 14:25:16.640', + '2025-04-22 14:25:16.640'), + (26, 'register', 'StopRegister', 'false', 'bool', 'is stop register', '2025-04-22 14:25:16.640', + '2025-04-22 14:25:16.640'), + (27, 'register', 'EnableTrial', 'false', 'bool', 'is enable trial', '2025-04-22 14:25:16.640', + '2025-04-22 14:25:16.640'), + (28, 'register', 'TrialSubscribe', '', 'int', 'Trial subscription', '2025-04-22 14:25:16.640', + '2025-04-22 14:25:16.640'), + (29, 'register', 'TrialTime', '24', 'int', 'Trial time', '2025-04-22 14:25:16.640', '2025-04-22 14:25:16.640'), + (30, 'register', 'TrialTimeUnit', 'Hour', 'string', 'Trial time unit', '2025-04-22 14:25:16.640', + '2025-04-22 14:25:16.640'), + (31, 'register', 'EnableIpRegisterLimit', 'false', 'bool', 'is enable IP register limit', + '2025-04-22 14:25:16.640', '2025-04-22 14:25:16.640'), + (32, 'register', 'IpRegisterLimit', '3', 'int', 'IP Register Limit', '2025-04-22 14:25:16.640', + '2025-04-22 14:25:16.640'), + (33, 'register', 'IpRegisterLimitDuration', '64', 'int', 'IP Register Limit Duration (minutes)', + '2025-04-22 14:25:16.640', '2025-04-22 14:25:16.640'), + (34, 'currency', 'Currency', 'USD', 'string', 'Currency', '2025-04-22 14:25:16.641', '2025-04-22 14:25:16.641'), + (35, 'currency', 'CurrencySymbol', '$', 'string', 'Currency Symbol', '2025-04-22 14:25:16.641', + '2025-04-22 14:25:16.641'), + (36, 'currency', 'CurrencyUnit', 'USD', 'string', 'Currency Unit', '2025-04-22 14:25:16.641', + '2025-04-22 14:25:16.641'), + (37, 'currency', 'AccessKey', '', 'string', 'Exchangerate Access Key', '2025-04-22 14:25:16.641', + '2025-04-22 14:25:16.641'), + (38, 'verify_code', 'VerifyCodeExpireTime', '300', 'int', 'Verify code expire time', '2025-04-22 14:25:16.641', + '2025-04-22 14:25:16.641'), + (39, 'verify_code', 'VerifyCodeLimit', '15', 'int', 'limits of verify code', '2025-04-22 14:25:16.641', + '2025-04-22 14:25:16.641'), + (40, 'verify_code', 'VerifyCodeInterval', '60', 'int', 'Interval of verify code', '2025-04-22 14:25:16.641', + '2025-04-22 14:25:16.641'), + (41, 'system', 'Version', '0.2.0(02002)', 'string', 'System Version', '2025-04-22 14:25:16.642', + '2025-04-22 14:25:16.642'); +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/initialize/migrate/database/01200-patch.sql b/initialize/migrate/database/01200-patch.sql deleted file mode 100644 index 7c2c000..0000000 --- a/initialize/migrate/database/01200-patch.sql +++ /dev/null @@ -1,54 +0,0 @@ --- 先检查 `email` 列是否存在,再删除 -SELECT COUNT(*) INTO @col_exists FROM information_schema.columns -WHERE table_schema = DATABASE() AND table_name = 'user' AND column_name = 'email'; - -SET @sql = IF(@col_exists > 0, 'ALTER TABLE `user` DROP COLUMN `email`', 'SELECT 1'); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- 先检查 `telephone` 列是否存在,再删除 -SELECT COUNT(*) INTO @col_exists FROM information_schema.columns -WHERE table_schema = DATABASE() AND table_name = 'user' AND column_name = 'telephone'; - -SET @sql = IF(@col_exists > 0, 'ALTER TABLE `user` DROP COLUMN `telephone`', 'SELECT 1'); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- 先检查 `telephone_area_code` 列是否存在,再删除 -SELECT COUNT(*) INTO @col_exists FROM information_schema.columns -WHERE table_schema = DATABASE() AND table_name = 'user' AND column_name = 'telephone_area_code'; - -SET @sql = IF(@col_exists > 0, 'ALTER TABLE `user` DROP COLUMN `telephone_area_code`', 'SELECT 1'); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - - --- 先检查 `idx_email` 索引是否存在,再删除 -SELECT COUNT(*) INTO @idx_exists FROM information_schema.statistics -WHERE table_schema = DATABASE() AND table_name = 'user' AND index_name = 'idx_email'; - -SET @sql = IF(@idx_exists > 0, 'ALTER TABLE `user` DROP INDEX `idx_email`', 'SELECT 1'); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- 先检查 `idx_telephone` 索引是否存在,再删除 -SELECT COUNT(*) INTO @idx_exists FROM information_schema.statistics -WHERE table_schema = DATABASE() AND table_name = 'user' AND index_name = 'idx_telephone'; - -SET @sql = IF(@idx_exists > 0, 'ALTER TABLE `user` DROP INDEX `idx_telephone`', 'SELECT 1'); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- 先检查 `idx_telephone_area_code` 索引是否存在,再删除 -SELECT COUNT(*) INTO @idx_exists FROM information_schema.statistics -WHERE table_schema = DATABASE() AND table_name = 'user' AND index_name = 'idx_telephone_area_code'; - -SET @sql = IF(@idx_exists > 0, 'ALTER TABLE `user` DROP INDEX `idx_telephone_area_code`', 'SELECT 1'); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; diff --git a/initialize/migrate/database/01201-patch.sql b/initialize/migrate/database/01201-patch.sql deleted file mode 100644 index d2f4364..0000000 --- a/initialize/migrate/database/01201-patch.sql +++ /dev/null @@ -1,118 +0,0 @@ -SET NAMES utf8mb4; -SET FOREIGN_KEY_CHECKS = 0; - --- 检查表是否存在,如果存在则跳过创建 -CREATE TABLE IF NOT EXISTS `oauth_config` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `platform` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'platform', - `config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'OAuth Configuration', - `redirect` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Redirect URL', - `enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Enabled', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`), - UNIQUE KEY `uni_oauth_config_platform` (`platform`) - ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- 插入记录时忽略重复记录 -BEGIN; -INSERT IGNORE INTO `oauth_config` (`id`, `platform`, `config`, `redirect`, `enabled`, `created_at`, `updated_at`) VALUES -(1, 'apple', '{\"team_id\":\"\",\"key_id\":\"\",\"client_id\":\"\",\"client_secret\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'), -(2, 'google', '{\"client_id\":\"\",\"client_secret\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'), -(3, 'github', '{\"client_id\":\"\",\"client_secret\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'), -(4, 'facebook', '{\"client_id\":\"\",\"client_secret\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'), -(5, 'telegram', '{\"bot\":\"\",\"bot_token\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'); -COMMIT; - --- 检测更新设置表 -BEGIN; -INSERT IGNORE INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`) VALUES -('sms', 'SmsEnabled', 'false', 'bool', '是否启用短信功能', NOW(), NOW()), -('sms', 'SmsKey', 'your-key', 'string', '短信服务用户名或Key',NOW(), NOW()), -('sms', 'SmsSecret', 'your-secret', 'string', '短信服务密码或Secret', NOW(), NOW()), -('sms', 'SmsSign', 'your-sign', 'string', '短信签名', NOW(), NOW()), -('sms', 'SmsTemplate', 'your-template', 'string', '短信模板ID', NOW(), NOW()), -('sms', 'SmsRegion', 'cn-hangzhou', 'string', '短信服务所在区域(适用于阿里云)', NOW(), NOW()), -('sms', 'SmsTemplate', '您的验证码是{{.Code}},请在5分钟内使用。', 'string', '自定义短信模板', NOW(), NOW()), -('sms', 'SmsTemplateCode', 'SMS_12345678', 'string', '阿里云国内短信模板代码',NOW(),NOW()), -('sms', 'SmsTemplateParam', '{\"code\":{{.Code}}}', 'string', '短信模板参数', NOW(), NOW()), -('sms', 'SmsPlatform', 'smsbao', 'string', '当前使用的短信平台', NOW(), NOW()), -('sms', 'SmsLimit', '10', 'int64', '可以发送的短信最大数量', NOW(), NOW()), -('sms', 'SmsInterval', '60', 'int64', '发送短信的时间间隔(单位:秒)',NOW(), NOW()), -('sms', 'SmsExpireTime', '300', 'int64', '短信验证码的过期时间(单位:秒)',NOW(), NOW()), -('email', 'EmailEnabled', 'true', 'bool', '启用邮箱登陆',NOW(), NOW()), -('email', 'EmailSmtpHost', '', 'string', '邮箱服务器地址', NOW(), NOW()), -('email', 'EmailSmtpPort', '465', 'int', '邮箱服务器端口',NOW(), NOW()), -('email', 'EmailSmtpUser', 'domain@f1shyu.com', 'string', '邮箱服务器用户名', NOW(), NOW()), -('email', 'EmailSmtpPass', 'password', 'string', '邮箱服务器密码', NOW(), NOW()), -('email', 'EmailSmtpFrom', 'domain@f1shyu.com', 'string', '发送邮件的邮箱',NOW(), NOW()), -('email', 'EmailSmtpSSL', 'true', 'bool', '邮箱服务器加密方式',NOW(), NOW()), -('email', 'EmailTemplate', '%s', 'string', '邮件模板',NOW(), NOW()), -('email', 'VerifyEmailTemplate', '', 'string', 'Verify Email template',NOW(), NOW()), -('email', 'MaintenanceEmailTemplate', '', 'string', 'Maintenance Email template',NOW(), NOW()), -('email', 'ExpirationEmailTemplate', '', 'string', 'Expiration Email template', NOW(), NOW()), -('email', 'EmailEnableVerify', 'true', 'bool', '是否开启邮箱验证', NOW(), NOW()), -('email', 'EmailEnableDomainSuffix', 'false', 'bool', '是否开启邮箱域名后缀限制',NOW(), NOW()), -('email', 'EmailDomainSuffixList', 'qq.com', 'string', '邮箱域名后缀列表',NOW(), NOW()); -COMMIT; - --- User Device -CREATE TABLE IF NOT EXISTS `user_device` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `user_id` bigint NOT NULL COMMENT 'User ID', - `device_number` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Number.', - `online` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Online', - `enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'EnableDeviceNumber', - `last_online` datetime(3) DEFAULT NULL COMMENT 'Last Online', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`), - KEY `idx_user_id` (`user_id`), - CONSTRAINT `fk_user_user_devices` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- Mobile -CREATE TABLE IF NOT EXISTS `sms` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `content` text COLLATE utf8mb4_general_ci, - `platform` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL, - `area_code` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL, - `telephone` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL, - `status` tinyint(1) DEFAULT '1', - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- Application Config -CREATE TABLE IF NOT EXISTS `application_config` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `app_id` bigint NOT NULL DEFAULT '0' COMMENT 'App id', - `encryption_key` text COLLATE utf8mb4_general_ci COMMENT 'Encryption Key', - `encryption_method` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Encryption Method', - `domains` text COLLATE utf8mb4_general_ci, - `startup_picture` text COLLATE utf8mb4_general_ci, - `startup_picture_skip_time` bigint NOT NULL DEFAULT '0' COMMENT 'Startup Picture Skip Time', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- Application Version -CREATE TABLE IF NOT EXISTS `application_version` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `url` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用地址', - `version` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用版本', - `platform` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用平台', - `is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT '默认版本', - `description` text COLLATE utf8mb4_general_ci COMMENT '更新描述', - `application_id` bigint DEFAULT NULL COMMENT '所属应用', - `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间', - `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - KEY `fk_application_application_versions` (`application_id`), - CONSTRAINT `fk_application_application_versions` FOREIGN KEY (`application_id`) REFERENCES `application` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -UPDATE `subscribe` SET `unit_time`='Month' WHERE unit_time = ''; - -SET FOREIGN_KEY_CHECKS = 1; diff --git a/initialize/migrate/database/01202-patch.sql b/initialize/migrate/database/01202-patch.sql deleted file mode 100644 index 95c3a14..0000000 --- a/initialize/migrate/database/01202-patch.sql +++ /dev/null @@ -1,44 +0,0 @@ - -SET NAMES utf8mb4; -SET FOREIGN_KEY_CHECKS = 0; - -DROP TABLE IF EXISTS `user_device`; --- User Device -CREATE TABLE IF NOT EXISTS `user_device` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `user_id` bigint NOT NULL COMMENT 'User ID', - `subscribe_id` bigint DEFAULT NULL COMMENT 'Subscribe ID', - `ip` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Ip.', - `Identifier` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Identifier.', - `user_agent` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device User Agent.', - `online` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Online', - `enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'EnableDeviceNumber', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`), - KEY `idx_user_id` (`user_id`), - CONSTRAINT `fk_user_user_devices` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- ---------------------------- --- Table structure for server_rule_group --- ---------------------------- -DROP TABLE IF EXISTS `server_rule_group`; -CREATE TABLE `server_rule_group` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Rule Group Name', - `icon` text COLLATE utf8mb4_general_ci COMMENT 'Rule Group Icon', - `description` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Rule Group Description', - `enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Rule Group Enable', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`), - UNIQUE KEY `unique_name` (`name`) -- Add unique constraint to `name` -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; --- ---------------------------- --- Records of server_rule_group --- ---------------------------- -BEGIN; -COMMIT; - -SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/initialize/migrate/database/02003_update_payment.down.sql b/initialize/migrate/database/02003_update_payment.down.sql new file mode 100644 index 0000000..51715c2 --- /dev/null +++ b/initialize/migrate/database/02003_update_payment.down.sql @@ -0,0 +1,72 @@ +-- migrations/02003_update_payment.down.sql +-- Purpose: Revert updates to payment and order tables +-- Author: PPanel Team, 2025-04-21 + +SET FOREIGN_KEY_CHECKS = 0; + +-- Drop payment_id column from order table (if exists) +SET @column_exists = (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'order' + AND COLUMN_NAME = 'payment_id'); +SET @sql = IF(@column_exists > 0, + 'ALTER TABLE `order` DROP COLUMN `payment_id`', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- Drop platform column from payment table (if exists) +SET @column_exists = (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'payment' + AND COLUMN_NAME = 'platform'); +SET @sql = IF(@column_exists > 0, + 'ALTER TABLE `payment` DROP COLUMN `platform`', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- Drop description column from payment table (if exists) +SET @column_exists = (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'payment' + AND COLUMN_NAME = 'description'); +SET @sql = IF(@column_exists > 0, + 'ALTER TABLE `payment` DROP COLUMN `description`', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- Drop token column from payment table (if exists) +SET @column_exists = (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'payment' + AND COLUMN_NAME = 'token'); +SET @sql = IF(@column_exists > 0, + 'ALTER TABLE `payment` DROP COLUMN `token`', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- Optionally restore mark column (if needed, adjust definition as per original schema) +SET @column_exists = (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'payment' + AND COLUMN_NAME = 'mark'); +SET @sql = IF(@column_exists = 0, + 'ALTER TABLE `payment` ADD COLUMN `mark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT \'Payment Mark\' AFTER `name`', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/initialize/migrate/database/02003_update_payment.up.sql b/initialize/migrate/database/02003_update_payment.up.sql new file mode 100644 index 0000000..3991cff --- /dev/null +++ b/initialize/migrate/database/02003_update_payment.up.sql @@ -0,0 +1,72 @@ +-- 2025-04-22 16:16:00 +-- Purpose: Update payment table +-- Author: PPanel Team, 2025-04-21 + +SET FOREIGN_KEY_CHECKS = 0; + +-- Alter the order table to add a payment_id column (if not exists) +SET @column_exists = (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'order' + AND COLUMN_NAME = 'payment_id'); +SET @sql = IF(@column_exists = 0, + 'ALTER TABLE `order` ADD COLUMN `payment_id` bigint NOT NULL DEFAULT \'-1\' COMMENT \'Payment Id\' AFTER `commission`', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- Alter the payment table to add a platform column (if not exists) +SET @column_exists = (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'payment' + AND COLUMN_NAME = 'platform'); +SET @sql = IF(@column_exists = 0, + 'ALTER TABLE `payment` ADD COLUMN `platform` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT \'Payment Platform\' AFTER `name`', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- Drop the mark column from the payment table (only if exists) +SET @column_exists = (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'payment' + AND COLUMN_NAME = 'mark'); +SET @sql = IF(@column_exists > 0, + 'ALTER TABLE `payment` DROP COLUMN `mark`', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- Alter the payment table to add a description column (if not exists) +SET @column_exists = (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'payment' + AND COLUMN_NAME = 'description'); +SET @sql = IF(@column_exists = 0, + 'ALTER TABLE `payment` ADD COLUMN `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT \'Payment Description\' AFTER `platform`', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- Alter the payment table to add a token column (if not exists) +SET @column_exists = (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'payment' + AND COLUMN_NAME = 'token'); +SET @sql = IF(@column_exists = 0, + 'ALTER TABLE `payment` ADD COLUMN `token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT \'Payment Token\' AFTER `description`', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/initialize/migrate/database/02004_rebuild_rule.down.sql b/initialize/migrate/database/02004_rebuild_rule.down.sql new file mode 100644 index 0000000..a818ad8 --- /dev/null +++ b/initialize/migrate/database/02004_rebuild_rule.down.sql @@ -0,0 +1,4 @@ +-- migrations/02003_rebuild_rule.up.sql +-- Purpose: Back rebuilding server rule table +-- Author: PPanel Team, 2025-04-21 +DROP TABLE IF EXISTS server_rule_group; \ No newline at end of file diff --git a/initialize/migrate/database/02004_rebuild_rule.up.sql b/initialize/migrate/database/02004_rebuild_rule.up.sql new file mode 100644 index 0000000..72f9b0f --- /dev/null +++ b/initialize/migrate/database/02004_rebuild_rule.up.sql @@ -0,0 +1,22 @@ +-- migrations/02003_rebuild_rule.up.sql +-- Purpose: rebuilding server rule table +-- Author: PPanel Team, 2025-04-21 + +DROP TABLE IF EXISTS `server_rule_group`; + +CREATE TABLE `server_rule_group` +( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + `name` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Rule Group Name', + `icon` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Rule Group Icon', + `tags` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Selected Node Tags', + `rules` MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Rules', + `enable` TINYINT(1) NOT NULL DEFAULT 1 COMMENT 'Rule Group Enable', + `created_at` DATETIME(3) COMMENT 'Creation Time', + `updated_at` DATETIME(3) COMMENT 'Update Time', + PRIMARY KEY (`id`), + UNIQUE KEY `uni_server_rule_group_name` (`name`), + INDEX `idx_enable` (`enable`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; \ No newline at end of file diff --git a/initialize/migrate/database/02005_device_online_record.down.sql b/initialize/migrate/database/02005_device_online_record.down.sql new file mode 100644 index 0000000..e571443 --- /dev/null +++ b/initialize/migrate/database/02005_device_online_record.down.sql @@ -0,0 +1,52 @@ +-- migrations/02004_create_user_device_online_record.down.sql +-- Purpose: Drop user device online record table +-- Author: PPanel Team, 2025-04-22 + +DROP TABLE IF EXISTS `user_device_online_record`; + +-- User subscribe table migration for removing finished_at column +SET @column_exists = (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'user_subscribe' + AND COLUMN_NAME = 'finished_at'); +SET @sql = IF(@column_exists > 0, + 'ALTER TABLE `user_subscribe` DROP COLUMN `finished_at`', + 'SELECT 1' + ); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- Application config table migration for removing invitation_link column + +SET @column_exists = (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'application_config' + AND COLUMN_NAME = 'invitation_link'); + +SET @sql = IF(@column_exists > 0, + 'ALTER TABLE `application_config` DROP COLUMN `invitation_link`', + 'SELECT 1' + ); + +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- Application config table migration for removing kr_website_id column +SET @column_exists = (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'application_config' + AND COLUMN_NAME = 'kr_website_id'); + +SET @sql = IF(@column_exists > 0, + 'ALTER TABLE `application_config` DROP COLUMN `kr_website_id`', + 'SELECT 1' + ); + +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; \ No newline at end of file diff --git a/initialize/migrate/database/02005_device_online_record.up.sql b/initialize/migrate/database/02005_device_online_record.up.sql new file mode 100644 index 0000000..e149f12 --- /dev/null +++ b/initialize/migrate/database/02005_device_online_record.up.sql @@ -0,0 +1,69 @@ +-- migrations/02005_create_user_device_online_record.up.sql +-- Purpose: Create table for tracking user device online records +-- Author: PPanel Team, 2025-04-22 + +CREATE TABLE IF NOT EXISTS `user_device_online_record` +( + `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `user_id` BIGINT NOT NULL COMMENT 'User ID', + `identifier` VARCHAR(255) NOT NULL COMMENT 'Device Identifier', + `online_time` DATETIME COMMENT 'Online Time', + `offline_time` DATETIME COMMENT 'Offline Time', + `online_seconds` BIGINT COMMENT 'Offline Seconds', + `duration_days` BIGINT COMMENT 'Duration Days', + `created_at` DATETIME COMMENT 'Creation Time' +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + + +-- User subscribe table migration for adding finished_at column + +SET @column_exists = (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'user_subscribe' + AND COLUMN_NAME = 'finished_at'); + +SET @sql = IF(@column_exists = 0, + 'ALTER TABLE `user_subscribe` ADD COLUMN `finished_at` DATETIME NULL COMMENT ''Subscribe Finished Time'' AFTER `expire_time`', + 'SELECT 1' + ); + +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + + +-- Application config table migration for adding Link column + +SET @column_exists = (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'application_config' + AND COLUMN_NAME = 'invitation_link'); + +SET @sql = IF(@column_exists = 0, + 'ALTER TABLE `application_config` ADD COLUMN `invitation_link` TEXT NULL DEFAULT NULL COMMENT ''Invitation Link'' AFTER `startup_picture_skip_time`', + 'SELECT 1' + ); + +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- Application config table migration for adding kr_website_id column +SET @column_exists = (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'application_config' + AND COLUMN_NAME = 'kr_website_id'); + +SET @sql = IF(@column_exists = 0, + 'ALTER TABLE `application_config` ADD COLUMN `kr_website_id` VARCHAR(255) NULL DEFAULT NULL COMMENT ''KR Website ID'' AFTER `invitation_link`', + 'SELECT 1' + ); + +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/initialize/migrate/database/02006_reset_subscribe_record.down.sql b/initialize/migrate/database/02006_reset_subscribe_record.down.sql new file mode 100644 index 0000000..38af374 --- /dev/null +++ b/initialize/migrate/database/02006_reset_subscribe_record.down.sql @@ -0,0 +1,5 @@ +-- migrations/02008_create_user_reset_subscribe_log.down.sql +-- Purpose: Drop user_reset_subscribe_log table +-- Author: PPanel Team, 2025-04-22 + +DROP TABLE IF EXISTS `user_reset_subscribe_log`; diff --git a/initialize/migrate/database/02006_reset_subscribe_record.up.sql b/initialize/migrate/database/02006_reset_subscribe_record.up.sql new file mode 100644 index 0000000..ef5affd --- /dev/null +++ b/initialize/migrate/database/02006_reset_subscribe_record.up.sql @@ -0,0 +1,17 @@ +-- migrations/02008_create_user_reset_subscribe_log.up.sql +-- Purpose: Create user_reset_subscribe_log table +-- Author: PPanel Team, 2025-04-22 + +CREATE TABLE IF NOT EXISTS `user_reset_subscribe_log` +( + `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `user_id` BIGINT NOT NULL COMMENT 'User ID', + `type` TINYINT(1) NOT NULL COMMENT 'Type: 1: Auto 2: Advance 3: Paid', + `order_no` VARCHAR(255) DEFAULT NULL COMMENT 'Order No.', + `user_subscribe_id` BIGINT NOT NULL COMMENT 'User Subscribe ID', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation Time', + INDEX `idx_user_id` (`user_id`), + INDEX `idx_user_subscribe_id` (`user_subscribe_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; diff --git a/initialize/migrate/database/02007_adapte_rule.down.sql b/initialize/migrate/database/02007_adapte_rule.down.sql new file mode 100644 index 0000000..ed5d22f --- /dev/null +++ b/initialize/migrate/database/02007_adapte_rule.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE `server_rule_group` +DROP COLUMN `default`, +DROP COLUMN `type`; diff --git a/initialize/migrate/database/02007_adapte_rule.up.sql b/initialize/migrate/database/02007_adapte_rule.up.sql new file mode 100644 index 0000000..0c30d54 --- /dev/null +++ b/initialize/migrate/database/02007_adapte_rule.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE `server_rule_group` +ADD COLUMN `default` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Is Default Group', +ADD COLUMN `type` VARCHAR(100) NOT NULL DEFAULT '' COMMENT 'Rule Group Type'; \ No newline at end of file diff --git a/initialize/migrate/database/02100_task.down.sql b/initialize/migrate/database/02100_task.down.sql new file mode 100644 index 0000000..0d1f763 --- /dev/null +++ b/initialize/migrate/database/02100_task.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS `email_task`; \ No newline at end of file diff --git a/initialize/migrate/database/02100_task.up.sql b/initialize/migrate/database/02100_task.up.sql new file mode 100644 index 0000000..d35e8c9 --- /dev/null +++ b/initialize/migrate/database/02100_task.up.sql @@ -0,0 +1,23 @@ +DROP TABLE IF EXISTS `email_task`; +CREATE TABLE `email_task` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID', + `subject` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Email Subject', + `content` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Email Content', + `recipient` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Email Recipient', + `scope` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Email Scope', + `register_start_time` datetime(3) DEFAULT NULL COMMENT 'Register Start Time', + `register_end_time` datetime(3) DEFAULT NULL COMMENT 'Register End Time', + `additional` text COLLATE utf8mb4_general_ci COMMENT 'Additional Information', + `scheduled` datetime(3) NOT NULL COMMENT 'Scheduled Time', + `interval` tinyint unsigned NOT NULL COMMENT 'Interval in Seconds', + `limit` bigint unsigned NOT NULL COMMENT 'Daily send limit', + `status` tinyint unsigned NOT NULL COMMENT 'Daily Status', + `errors` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Errors', + `total` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'Total Number', + `current` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'Current Number', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/initialize/migrate/database/02101_subscribe_application.down.sql b/initialize/migrate/database/02101_subscribe_application.down.sql new file mode 100644 index 0000000..bca110a --- /dev/null +++ b/initialize/migrate/database/02101_subscribe_application.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS `subscribe_application`; \ No newline at end of file diff --git a/initialize/migrate/database/02101_subscribe_application.up.sql b/initialize/migrate/database/02101_subscribe_application.up.sql new file mode 100644 index 0000000..c46bc2b --- /dev/null +++ b/initialize/migrate/database/02101_subscribe_application.up.sql @@ -0,0 +1,27 @@ +DROP TABLE IF EXISTS `subscribe_application`; +CREATE TABLE IF NOT EXISTS `subscribe_application` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Application Name', + `icon` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Application Icon', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Application Description', + `scheme` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Application Scheme', + `user_agent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'User Agent', + `is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Default Application', + `subscribe_template` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Subscribe Template', + `output_format` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'yaml' COMMENT 'Output Format', + `download_link` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Download Link', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- ---------------------------- +-- Records of subscribe_application +-- ---------------------------- +BEGIN; +INSERT INTO `subscribe_application` (`id`, `name`, `icon`, `description`, `scheme`, `user_agent`, `is_default`, `subscribe_template`, `output_format`, `download_link`, `created_at`, `updated_at`) VALUES (1, 'Default', '', '', '', 'default', 1, '{{- $GiB := 1073741824.0 -}}\n{{- $used := printf \"%.2f\" (divf (add (.UserInfo.Download | default 0 | float64) (.UserInfo.Upload | default 0 | float64)) $GiB) -}}\n{{- $traffic := (.UserInfo.Traffic | default 0 | float64) -}}\n{{- $total := printf \"%.2f\" (divf $traffic $GiB) -}}\n\n{{- $exp := \"\" -}}\n{{- $expStr := printf \"%v\" .UserInfo.ExpiredAt -}}\n{{- if regexMatch `^[0-9]+$` $expStr -}}\n {{- $ts := $expStr | float64 -}}\n {{- $sec := ternary (divf $ts 1000.0) $ts (ge (len $expStr) 13) -}}\n {{- $exp = (date \"2006-01-02 15:04:05\" (unixEpoch ($sec | int64))) -}}\n{{- else -}}\n {{- $exp = $expStr -}}\n{{- end -}}\n\nREMARKS={{ .SiteName }}-{{ .SubscribeName }}\nSTATUS=Traffic: {{ $used }} GiB/{{ $total }} GiB | Expires: {{ $exp }}\n\n{{- range $proxy := .Proxies }}\n {{- $server := $proxy.Server -}}\n {{- if and (contains $proxy.Server \":\") (not (hasPrefix \"[\" $proxy.Server)) -}}\n {{- $server = printf \"[%s]\" $proxy.Server -}}\n {{- end -}}\n\n {{- $sni := default \"\" $proxy.SNI -}}\n {{- if eq $sni \"\" -}}\n {{- $sni = default \"\" $proxy.Host -}}\n {{- end -}}\n {{- if and (eq $sni \"\") (not (or (regexMatch \"^[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+$\" $proxy.Server) (contains $proxy.Server \":\"))) -}}\n {{- $sni = $proxy.Server -}}\n {{- end -}}\n\n {{- $common := \"udp=1&tfo=1\" -}}\n\n {{- $password := $.UserInfo.Password -}}\n {{- if and (eq $proxy.Type \"shadowsocks\") (ne (default \"\" $proxy.ServerKey) \"\") -}}\n {{- $method := $proxy.Method -}}\n {{- if or (hasPrefix \"2022-blake3-\" $method) (eq $method \"2022-blake3-aes-128-gcm\") (eq $method \"2022-blake3-aes-256-gcm\") -}}\n {{- $userKeyLen := ternary 16 32 (hasSuffix \"128-gcm\" $method) -}}\n {{- $pwdStr := printf \"%s\" $password -}}\n {{- $userKey := ternary $pwdStr (trunc $userKeyLen $pwdStr) (le (len $pwdStr) $userKeyLen) -}}\n {{- $serverB64 := b64enc $proxy.ServerKey -}}\n {{- $userB64 := b64enc $userKey -}}\n {{- $password = printf \"%s:%s\" $serverB64 $userB64 -}}\n {{- end -}}\n {{- end -}}\n\n {{- if eq $proxy.Type \"shadowsocks\" }}\nss://{{ printf \"%s:%s\" $proxy.Method $password | b64enc }}@{{ $server }}:{{ $proxy.Port }}?{{ $common }}#{{ $proxy.Name }}\n {{- else if eq $proxy.Type \"vmess\" }}\nvmess://{{ (dict \"v\" \"2\" \"ps\" $proxy.Name \"add\" $proxy.Server \"port\" (printf \"%d\" $proxy.Port) \"id\" $password \"aid\" \"0\" \"net\" (default \"tcp\" $proxy.Transport) \"type\" \"none\" \"host\" (default \"\" $proxy.Host) \"path\" (default \"\" $proxy.Path) \"tls\" (ternary \"tls\" \"\" (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\"))) \"sni\" $sni) | toJson | b64enc }}\n {{- else if eq $proxy.Type \"vless\" }}\nvless://{{ $password }}@{{ $server }}:{{ $proxy.Port }}?encryption=none{{- if ne (default \"\" $proxy.Flow) \"\" }}&flow={{ $proxy.Flow }}{{- end }}{{- if ne $proxy.Transport \"\" }}&type={{ $proxy.Transport }}{{- end }}{{- if and (eq $proxy.Transport \"ws\") (ne (default \"\" $proxy.Host) \"\") }}&host={{ $proxy.Host }}{{- end }}{{- if and (eq $proxy.Transport \"ws\") (ne (default \"\" $proxy.Path) \"\") }}&path={{ $proxy.Path | urlquery }}{{- end }}{{- if and (eq $proxy.Transport \"grpc\") (ne (default \"\" $proxy.ServiceName) \"\") }}&serviceName={{ $proxy.ServiceName }}{{- end }}{{- if or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\") }}&security={{ $proxy.Security }}{{- end }}{{- if ne $sni \"\" }}&sni={{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}&allowInsecure=1{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}&fp={{ $proxy.Fingerprint }}{{- end }}{{- if and (eq $proxy.Security \"reality\") (ne (default \"\" $proxy.RealityPublicKey) \"\") }}&pbk={{ $proxy.RealityPublicKey }}{{- end }}{{- if and (eq $proxy.Security \"reality\") (ne (default \"\" $proxy.RealityShortId) \"\") }}&sid={{ $proxy.RealityShortId }}{{- end }}&{{ $common }}#{{ $proxy.Name }}\n {{- else if eq $proxy.Type \"trojan\" }}\ntrojan://{{ $password }}@{{ $server }}:{{ $proxy.Port }}{{- if or (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) (ne $sni \"\")) (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) $proxy.AllowInsecure) (ne $proxy.Transport \"\") }}?{{- end }}{{- if and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) (ne $sni \"\") }}sni={{ $sni }}{{- end }}{{- if and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) $proxy.AllowInsecure }}{{- if and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) (ne $sni \"\") }}&{{- end }}allowInsecure=1{{- end }}{{- if ne $proxy.Transport \"\" }}{{- if or (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) (ne $sni \"\")) (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) $proxy.AllowInsecure) }}&{{- end }}type={{ $proxy.Transport }}{{- end }}{{- if and (eq $proxy.Transport \"ws\") (ne (default \"\" $proxy.Host) \"\") }}{{- if or (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) (ne $sni \"\")) (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) $proxy.AllowInsecure) (ne $proxy.Transport \"\") }}&{{- end }}host={{ $proxy.Host }}{{- end }}{{- if and (eq $proxy.Transport \"ws\") (ne (default \"\" $proxy.Path) \"\") }}{{- if or (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) (ne $sni \"\")) (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) $proxy.AllowInsecure) (ne $proxy.Transport \"\") (and (eq $proxy.Transport \"ws\") (ne (default \"\" $proxy.Host) \"\")) }}&{{- end }}path={{ $proxy.Path | urlquery }}{{- end }}{{- if or (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) (ne $sni \"\")) (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) $proxy.AllowInsecure) (ne $proxy.Transport \"\") }}&{{ $common }}{{- else }}?{{ $common }}{{- end }}#{{ $proxy.Name }}\n {{- else if eq $proxy.Type \"hysteria2\" }}\nhysteria2://{{ $server }}:{{ $proxy.Port }}{{- if or (ne $password \"\") (ne $sni \"\") $proxy.AllowInsecure (ne (default \"\" $proxy.ObfsPassword) \"\") (ne (default \"\" $proxy.HopPorts) \"\") }}?{{- end }}{{- if ne $password \"\" }}auth={{ $password }}{{- end }}{{- if ne $sni \"\" }}{{- if ne $password \"\" }}&{{- end }}sni={{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}{{- if or (ne $password \"\") (ne $sni \"\") }}&{{- end }}insecure=1{{- end }}{{- if ne (default \"\" $proxy.ObfsPassword) \"\" }}{{- if or (ne $password \"\") (ne $sni \"\") $proxy.AllowInsecure }}&{{- end }}obfs=salamander&obfs-password={{ $proxy.ObfsPassword }}{{- end }}{{- if ne (default \"\" $proxy.HopPorts) \"\" }}{{- if or (ne $password \"\") (ne $sni \"\") $proxy.AllowInsecure (ne (default \"\" $proxy.ObfsPassword) \"\") }}&{{- end }}mport={{ $proxy.HopPorts }}{{- end }}{{- if or (ne $password \"\") (ne $sni \"\") $proxy.AllowInsecure (ne (default \"\" $proxy.ObfsPassword) \"\") (ne (default \"\" $proxy.HopPorts) \"\") }}&{{ $common }}{{- else }}?{{ $common }}{{- end }}#{{ $proxy.Name }}\n {{- else if eq $proxy.Type \"tuic\" }}\ntuic://{{ $password }}:{{ $password }}@{{ $server }}:{{ $proxy.Port }}{{- if or (ne (default \"\" $proxy.CongestionController) \"\") (ne (default \"\" $proxy.UDPRelayMode) \"\") $proxy.ReduceRtt $proxy.DisableSNI (ne $sni \"\") $proxy.AllowInsecure }}?{{- end }}{{- if ne (default \"\" $proxy.CongestionController) \"\" }}congestion_controller={{ $proxy.CongestionController }}{{- end }}{{- if ne (default \"\" $proxy.UDPRelayMode) \"\" }}{{- if ne (default \"\" $proxy.CongestionController) \"\" }}&{{- end }}udp_relay_mode={{ $proxy.UDPRelayMode }}{{- end }}{{- if $proxy.ReduceRtt }}{{- if or (ne (default \"\" $proxy.CongestionController) \"\") (ne (default \"\" $proxy.UDPRelayMode) \"\") }}&{{- end }}reduce_rtt=1{{- end }}{{- if $proxy.DisableSNI }}{{- if or (ne (default \"\" $proxy.CongestionController) \"\") (ne (default \"\" $proxy.UDPRelayMode) \"\") $proxy.ReduceRtt }}&{{- end }}disable_sni=1{{- end }}{{- if ne $sni \"\" }}{{- if or (ne (default \"\" $proxy.CongestionController) \"\") (ne (default \"\" $proxy.UDPRelayMode) \"\") $proxy.ReduceRtt $proxy.DisableSNI }}&{{- end }}sni={{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}{{- if or (ne (default \"\" $proxy.CongestionController) \"\") (ne (default \"\" $proxy.UDPRelayMode) \"\") $proxy.ReduceRtt $proxy.DisableSNI (ne $sni \"\") }}&{{- end }}allow_insecure=1{{- end }}{{- if or (ne (default \"\" $proxy.CongestionController) \"\") (ne (default \"\" $proxy.UDPRelayMode) \"\") $proxy.ReduceRtt $proxy.DisableSNI (ne $sni \"\") $proxy.AllowInsecure }}&{{ $common }}{{- else }}?{{ $common }}{{- end }}#{{ $proxy.Name }}\n {{- else if eq $proxy.Type \"anytls\" }}\nanytls://{{ $password }}@{{ $server }}:{{ $proxy.Port }}{{- if ne $sni \"\" }}?sni={{ $sni }}&{{ $common }}{{- else }}?{{ $common }}{{- end }}#{{ $proxy.Name }}\n {{- end }}\n{{- end }}\n\n{{- range $proxy := .Proxies }}\n {{- if not (or (eq $proxy.Type \"shadowsocks\") (eq $proxy.Type \"vmess\") (eq $proxy.Type \"vless\") (eq $proxy.Type \"trojan\") (eq $proxy.Type \"hysteria2\") (eq $proxy.Type \"tuic\") (eq $proxy.Type \"anytls\")) }}\n# Skipped (unsupported protocol): {{ $proxy.Name }} ({{ $proxy.Type }})\n {{- end }}\n{{- end }}\n', 'base64', '{}', '2025-08-12 22:57:56.711', '2025-08-15 21:45:20.181'); +INSERT INTO `subscribe_application` (`id`, `name`, `icon`, `description`, `scheme`, `user_agent`, `is_default`, `subscribe_template`, `output_format`, `download_link`, `created_at`, `updated_at`) VALUES (2, 'Shadowrocket', '', '', 'shadowrocket://add/sub://${window.btoa(url)}?remark=${encodeURIComponent(name)}', 'Shadowrocket', 0, '{{- $GiB := 1073741824.0 -}}\n{{- $used := printf \"%.2f\" (divf (add (.UserInfo.Download | default 0 | float64) (.UserInfo.Upload | default 0 | float64)) $GiB) -}}\n{{- $traffic := (.UserInfo.Traffic | default 0 | float64) -}}\n{{- $total := printf \"%.2f\" (divf $traffic $GiB) -}}\n\n{{- $exp := \"\" -}}\n{{- $expStr := printf \"%v\" .UserInfo.ExpiredAt -}}\n{{- if regexMatch `^[0-9]+$` $expStr -}}\n {{- $ts := $expStr | float64 -}}\n {{- $sec := ternary (divf $ts 1000.0) $ts (ge (len $expStr) 13) -}}\n {{- $exp = (date \"2006-01-02 15:04:05\" (unixEpoch ($sec | int64))) -}}\n{{- else -}}\n {{- $exp = $expStr -}}\n{{- end -}}\n\nREMARKS={{ .SiteName }}-{{ .SubscribeName }}\nSTATUS=Traffic: {{ $used }} GiB/{{ $total }} GiB | Expires: {{ $exp }}\n\n{{- range $proxy := .Proxies }}\n {{- $server := $proxy.Server -}}\n {{- if and (contains $proxy.Server \":\") (not (hasPrefix \"[\" $proxy.Server)) -}}\n {{- $server = printf \"[%s]\" $proxy.Server -}}\n {{- end -}}\n\n {{- $sni := default \"\" $proxy.SNI -}}\n {{- if eq $sni \"\" -}}\n {{- $sni = default \"\" $proxy.Host -}}\n {{- end -}}\n {{- if and (eq $sni \"\") (not (or (regexMatch \"^[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+$\" $proxy.Server) (contains $proxy.Server \":\"))) -}}\n {{- $sni = $proxy.Server -}}\n {{- end -}}\n\n {{- $common := \"udp=1&tfo=1\" -}}\n\n {{- $password := $.UserInfo.Password -}}\n {{- if and (eq $proxy.Type \"shadowsocks\") (ne (default \"\" $proxy.ServerKey) \"\") -}}\n {{- $method := $proxy.Method -}}\n {{- if or (hasPrefix \"2022-blake3-\" $method) (eq $method \"2022-blake3-aes-128-gcm\") (eq $method \"2022-blake3-aes-256-gcm\") -}}\n {{- $userKeyLen := ternary 16 32 (hasSuffix \"128-gcm\" $method) -}}\n {{- $pwdStr := printf \"%s\" $password -}}\n {{- $userKey := ternary $pwdStr (trunc $userKeyLen $pwdStr) (le (len $pwdStr) $userKeyLen) -}}\n {{- $serverB64 := b64enc $proxy.ServerKey -}}\n {{- $userB64 := b64enc $userKey -}}\n {{- $password = printf \"%s:%s\" $serverB64 $userB64 -}}\n {{- end -}}\n {{- end -}}\n\n {{- if eq $proxy.Type \"shadowsocks\" }}\nss://{{ printf \"%s:%s\" (default \"aes-128-gcm\" $proxy.Method) $password | b64enc }}@{{ $server }}:{{ $proxy.Port }}?{{ $common }}#{{ $proxy.Name }}\n {{- else if eq $proxy.Type \"vmess\" }}\nvmess://{{ (dict \"v\" \"2\" \"ps\" $proxy.Name \"add\" $proxy.Server \"port\" (printf \"%d\" $proxy.Port) \"id\" $password \"aid\" \"0\" \"net\" (default \"tcp\" $proxy.Transport) \"type\" \"none\" \"host\" (default \"\" $proxy.Host) \"path\" (default \"\" $proxy.Path) \"tls\" (ternary \"tls\" \"\" (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\"))) \"sni\" $sni) | toJson | b64enc }}\n {{- else if eq $proxy.Type \"vless\" }}\nvless://{{ $password }}@{{ $server }}:{{ $proxy.Port }}?encryption=none{{- if ne (default \"\" $proxy.Flow) \"\" }}&flow={{ $proxy.Flow }}{{- end }}{{- if ne $proxy.Transport \"\" }}&type={{ $proxy.Transport }}{{- end }}{{- if and (eq $proxy.Transport \"ws\") (ne (default \"\" $proxy.Host) \"\") }}&host={{ $proxy.Host }}{{- end }}{{- if and (eq $proxy.Transport \"ws\") (ne (default \"\" $proxy.Path) \"\") }}&path={{ $proxy.Path | urlquery }}{{- end }}{{- if and (eq $proxy.Transport \"grpc\") (ne (default \"\" $proxy.ServiceName) \"\") }}&serviceName={{ $proxy.ServiceName }}{{- end }}{{- if or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\") }}&security={{ $proxy.Security }}{{- end }}{{- if ne $sni \"\" }}&sni={{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}&allowInsecure=1{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}&fp={{ $proxy.Fingerprint }}{{- end }}{{- if and (eq $proxy.Security \"reality\") (ne (default \"\" $proxy.RealityPublicKey) \"\") }}&pbk={{ $proxy.RealityPublicKey }}{{- end }}{{- if and (eq $proxy.Security \"reality\") (ne (default \"\" $proxy.RealityShortId) \"\") }}&sid={{ $proxy.RealityShortId }}{{- end }}&{{ $common }}#{{ $proxy.Name }}\n {{- else if eq $proxy.Type \"trojan\" }}\ntrojan://{{ $password }}@{{ $server }}:{{ $proxy.Port }}{{- if or (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) (ne $sni \"\")) (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) $proxy.AllowInsecure) (ne $proxy.Transport \"\") }}?{{- end }}{{- if and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) (ne $sni \"\") }}sni={{ $sni }}{{- end }}{{- if and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) $proxy.AllowInsecure }}{{- if and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) (ne $sni \"\") }}&{{- end }}allowInsecure=1{{- end }}{{- if ne $proxy.Transport \"\" }}{{- if or (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) (ne $sni \"\")) (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) $proxy.AllowInsecure) }}&{{- end }}type={{ $proxy.Transport }}{{- end }}{{- if and (eq $proxy.Transport \"ws\") (ne (default \"\" $proxy.Host) \"\") }}{{- if or (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) (ne $sni \"\")) (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) $proxy.AllowInsecure) (ne $proxy.Transport \"\") }}&{{- end }}host={{ $proxy.Host }}{{- end }}{{- if and (eq $proxy.Transport \"ws\") (ne (default \"\" $proxy.Path) \"\") }}{{- if or (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) (ne $sni \"\")) (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) $proxy.AllowInsecure) (ne $proxy.Transport \"\") (and (eq $proxy.Transport \"ws\") (ne (default \"\" $proxy.Host) \"\")) }}&{{- end }}path={{ $proxy.Path | urlquery }}{{- end }}{{- if or (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) (ne $sni \"\")) (and (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) $proxy.AllowInsecure) (ne $proxy.Transport \"\") }}&{{ $common }}{{- else }}?{{ $common }}{{- end }}#{{ $proxy.Name }}\n {{- else if eq $proxy.Type \"hysteria2\" }}\nhysteria2://{{ $server }}:{{ $proxy.Port }}{{- if or (ne $password \"\") (ne $sni \"\") $proxy.AllowInsecure (ne (default \"\" $proxy.ObfsPassword) \"\") (ne (default \"\" $proxy.HopPorts) \"\") }}?{{- end }}{{- if ne $password \"\" }}auth={{ $password }}{{- end }}{{- if ne $sni \"\" }}{{- if ne $password \"\" }}&{{- end }}sni={{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}{{- if or (ne $password \"\") (ne $sni \"\") }}&{{- end }}insecure=1{{- end }}{{- if ne (default \"\" $proxy.ObfsPassword) \"\" }}{{- if or (ne $password \"\") (ne $sni \"\") $proxy.AllowInsecure }}&{{- end }}obfs=salamander&obfs-password={{ $proxy.ObfsPassword }}{{- end }}{{- if ne (default \"\" $proxy.HopPorts) \"\" }}{{- if or (ne $password \"\") (ne $sni \"\") $proxy.AllowInsecure (ne (default \"\" $proxy.ObfsPassword) \"\") }}&{{- end }}mport={{ $proxy.HopPorts }}{{- end }}{{- if or (ne $password \"\") (ne $sni \"\") $proxy.AllowInsecure (ne (default \"\" $proxy.ObfsPassword) \"\") (ne (default \"\" $proxy.HopPorts) \"\") }}&{{ $common }}{{- else }}?{{ $common }}{{- end }}#{{ $proxy.Name }}\n {{- else if eq $proxy.Type \"tuic\" }}\ntuic://{{ $password }}:{{ $password }}@{{ $server }}:{{ $proxy.Port }}{{- if or (ne (default \"\" $proxy.CongestionController) \"\") (ne (default \"\" $proxy.UDPRelayMode) \"\") $proxy.ReduceRtt $proxy.DisableSNI (ne $sni \"\") $proxy.AllowInsecure }}?{{- end }}{{- if ne (default \"\" $proxy.CongestionController) \"\" }}congestion_controller={{ $proxy.CongestionController }}{{- end }}{{- if ne (default \"\" $proxy.UDPRelayMode) \"\" }}{{- if ne (default \"\" $proxy.CongestionController) \"\" }}&{{- end }}udp_relay_mode={{ $proxy.UDPRelayMode }}{{- end }}{{- if $proxy.ReduceRtt }}{{- if or (ne (default \"\" $proxy.CongestionController) \"\") (ne (default \"\" $proxy.UDPRelayMode) \"\") }}&{{- end }}reduce_rtt=1{{- end }}{{- if $proxy.DisableSNI }}{{- if or (ne (default \"\" $proxy.CongestionController) \"\") (ne (default \"\" $proxy.UDPRelayMode) \"\") $proxy.ReduceRtt }}&{{- end }}disable_sni=1{{- end }}{{- if ne $sni \"\" }}{{- if or (ne (default \"\" $proxy.CongestionController) \"\") (ne (default \"\" $proxy.UDPRelayMode) \"\") $proxy.ReduceRtt $proxy.DisableSNI }}&{{- end }}sni={{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}{{- if or (ne (default \"\" $proxy.CongestionController) \"\") (ne (default \"\" $proxy.UDPRelayMode) \"\") $proxy.ReduceRtt $proxy.DisableSNI (ne $sni \"\") }}&{{- end }}allow_insecure=1{{- end }}{{- if or (ne (default \"\" $proxy.CongestionController) \"\") (ne (default \"\" $proxy.UDPRelayMode) \"\") $proxy.ReduceRtt $proxy.DisableSNI (ne $sni \"\") $proxy.AllowInsecure }}&{{ $common }}{{- else }}?{{ $common }}{{- end }}#{{ $proxy.Name }}\n {{- else if eq $proxy.Type \"anytls\" }}\nanytls://{{ $password }}@{{ $server }}:{{ $proxy.Port }}{{- if ne $sni \"\" }}?sni={{ $sni }}&{{ $common }}{{- else }}?{{ $common }}{{- end }}#{{ $proxy.Name }}\n {{- else }}\n# Unsupported protocol: {{ $proxy.Type }} - {{ $proxy.Name }}\n {{- end }}\n{{- end }}', 'base64', '{}', '2025-08-12 23:03:50.004', '2025-08-15 22:01:39.221'); +INSERT INTO `subscribe_application` (`id`, `name`, `icon`, `description`, `scheme`, `user_agent`, `is_default`, `subscribe_template`, `output_format`, `download_link`, `created_at`, `updated_at`) VALUES (3, 'Clash', '', '', 'clash://install-config?url=${url}&name=${name}', 'Clash', 0, '{{- $GiB := 1073741824.0 -}}\n{{- $used := printf \"%.2f\" (divf (add (.UserInfo.Download | default 0 | float64) (.UserInfo.Upload | default 0 | float64)) $GiB) -}}\n{{- $traffic := (.UserInfo.Traffic | default 0 | float64) -}}\n{{- $total := printf \"%.2f\" (divf $traffic $GiB) -}}\n\n{{- $exp := \"\" -}}\n{{- $expStr := printf \"%v\" .UserInfo.ExpiredAt -}}\n{{- if regexMatch `^[0-9]+$` $expStr -}}\n {{- $ts := $expStr | float64 -}}\n {{- $sec := ternary (divf $ts 1000.0) $ts (ge (len $expStr) 13) -}}\n {{- $exp = (date \"2006-01-02 15:04:05\" (unixEpoch ($sec | int64))) -}}\n{{- else -}}\n {{- $exp = $expStr -}}\n{{- end -}}\n\n{{- $supportedProxies := list -}}\n{{- range $proxy := .Proxies -}}\n {{- if or (eq $proxy.Type \"shadowsocks\") (eq $proxy.Type \"vmess\") (eq $proxy.Type \"vless\") (eq $proxy.Type \"trojan\") (eq $proxy.Type \"hysteria2\") (eq $proxy.Type \"tuic\") -}}\n {{- $supportedProxies = append $supportedProxies $proxy -}}\n {{- end -}}\n{{- end -}}\n\n{{- $proxyNames := \"\" -}}\n{{- range $proxy := $supportedProxies -}}\n {{- if eq $proxyNames \"\" -}}\n {{- $proxyNames = $proxy.Name -}}\n {{- else -}}\n {{- $proxyNames = printf \"%s, %s\" $proxyNames $proxy.Name -}}\n {{- end -}}\n{{- end -}}\n\n# {{ .SiteName }}-{{ .SubscribeName }}\n# Traffic: {{ $used }} GiB/{{ $total }} GiB | Expires: {{ $exp }}\n\nmode: rule\nipv6: true\nallow-lan: true\nbind-address: ''*''\nmixed-port: 6088\nlog-level: error\nunified-delay: true\ntcp-concurrent: true\nexternal-controller: ''0.0.0.0:9090''\ntun:\n enable: true\n stack: system\n auto-route: true\ndns:\n enable: true\n cache-algorithm: arc\n listen: ''0.0.0.0:1053''\n ipv6: true\n enhanced-mode: fake-ip\n fake-ip-range: 198.18.0.1/16\n fake-ip-filter: [''*.lan'', lens.l.google.com, ''*.srv.nintendo.net'', ''*.stun.playstation.net'', ''xbox.*.*.microsoft.com'', ''*.xboxlive.com'', ''*.msftncsi.com'', ''*.msftconnecttest.com'']\n default-nameserver: [119.29.29.29, 223.5.5.5]\n nameserver: [system, 119.29.29.29, 223.5.5.5]\n fallback: [8.8.8.8, 1.1.1.1]\n fallback-filter: { geoip: true, geoip-code: CN }\n\nproxies:\n{{- range $proxy := $supportedProxies }}\n {{- $server := $proxy.Server -}}\n {{- if and (contains $proxy.Server \":\") (not (hasPrefix \"[\" $proxy.Server)) -}}\n {{- $server = printf \"[%s]\" $proxy.Server -}}\n {{- end -}}\n\n {{- $sni := default \"\" $proxy.SNI -}}\n {{- if eq $sni \"\" -}}\n {{- $sni = default \"\" $proxy.Host -}}\n {{- end -}}\n {{- if and (eq $sni \"\") (not (or (regexMatch \"^[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+$\" $proxy.Server) (contains $proxy.Server \":\"))) -}}\n {{- $sni = $proxy.Server -}}\n {{- end -}}\n\n {{- $password := $.UserInfo.Password -}}\n {{- if and (eq $proxy.Type \"shadowsocks\") (ne (default \"\" $proxy.ServerKey) \"\") -}}\n {{- $method := $proxy.Method -}}\n {{- if or (hasPrefix \"2022-blake3-\" $method) (eq $method \"2022-blake3-aes-128-gcm\") (eq $method \"2022-blake3-aes-256-gcm\") -}}\n {{- $userKeyLen := ternary 16 32 (hasSuffix \"128-gcm\" $method) -}}\n {{- $pwdStr := printf \"%s\" $password -}}\n {{- $userKey := ternary $pwdStr (trunc $userKeyLen $pwdStr) (le (len $pwdStr) $userKeyLen) -}}\n {{- $serverB64 := b64enc $proxy.ServerKey -}}\n {{- $userB64 := b64enc $userKey -}}\n {{- $password = printf \"%s:%s\" $serverB64 $userB64 -}}\n {{- end -}}\n {{- end -}}\n\n {{- $common := \"udp: true, tfo: true\" -}}\n\n {{- if eq $proxy.Type \"shadowsocks\" }}\n - { name: {{ $proxy.Name | quote }}, type: ss, server: {{ $server }}, port: {{ $proxy.Port }}, cipher: {{ default \"aes-128-gcm\" $proxy.Method }}, password: {{ $password }}, {{ $common }}{{- if ne (default \"\" $proxy.Transport) \"\" }}, plugin: obfs, plugin-opts: { mode: http, host: {{ $sni }} }{{- end }} }\n {{- else if eq $proxy.Type \"vmess\" }}\n - { name: {{ $proxy.Name | quote }}, type: vmess, server: {{ $server }}, port: {{ $proxy.Port }}, uuid: {{ $password }}, alterId: 0, cipher: auto, {{ $common }}{{- if or (eq $proxy.Transport \"websocket\") (eq $proxy.Transport \"ws\") }}, network: ws, ws-opts: { path: {{ default \"/\" $proxy.Path }}{{- if ne (default \"\" $proxy.Host) \"\" }}, headers: { Host: {{ $proxy.Host }} }{{- end }} }{{- else if eq $proxy.Transport \"http\" }}, network: http, http-opts: { method: GET, path: [{{ default \"/\" $proxy.Path | quote }}]{{- if ne (default \"\" $proxy.Host) \"\" }}, headers: { Host: [{{ $proxy.Host | quote }}] }{{- end }} }{{- else if eq $proxy.Transport \"grpc\" }}, network: grpc, grpc-opts: { grpc-service-name: {{ default \"grpc\" $proxy.ServiceName }} }{{- end }}{{- if or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\") }}, tls: true{{- end }}{{- if ne $sni \"\" }}, servername: {{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify: true{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}, fingerprint: {{ $proxy.Fingerprint }}{{- end }} }\n {{- else if eq $proxy.Type \"vless\" }}\n - { name: {{ $proxy.Name | quote }}, type: vless, server: {{ $server }}, port: {{ $proxy.Port }}, uuid: {{ $password }}, {{ $common }}{{- if or (eq $proxy.Transport \"ws\") (eq $proxy.Transport \"websocket\") }}, network: ws, ws-opts: { path: {{ default \"/\" $proxy.Path }}{{- if ne (default \"\" $proxy.Host) \"\" }}, headers: { Host: {{ $proxy.Host }} }{{- end }} }{{- else if eq $proxy.Transport \"http\" }}, network: http, http-opts: { method: GET, path: [{{ default \"/\" $proxy.Path | quote }}]{{- if ne (default \"\" $proxy.Host) \"\" }}, headers: { Host: [{{ $proxy.Host | quote }}] }{{- end }} }{{- else if eq $proxy.Transport \"grpc\" }}, network: grpc, grpc-opts: { grpc-service-name: {{ default \"grpc\" $proxy.ServiceName }} }{{- end }}{{- if ne $sni \"\" }}, servername: {{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify: true{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}, fingerprint: {{ $proxy.Fingerprint }}{{- end }}{{- if and (eq $proxy.Security \"reality\") (ne (default \"\" $proxy.RealityPublicKey) \"\") }}, reality-opts: { public-key: {{ $proxy.RealityPublicKey }}{{- if ne (default \"\" $proxy.RealityShortId) \"\" }}, short-id: {{ $proxy.RealityShortId }}{{- end }} }{{- end }}{{- if ne (default \"\" $proxy.Flow) \"\" }}, flow: {{ $proxy.Flow }}{{- end }} }\n {{- else if eq $proxy.Type \"trojan\" }}\n - { name: {{ $proxy.Name | quote }}, type: trojan, server: {{ $server }}, port: {{ $proxy.Port }}, password: {{ $password }}, {{ $common }}{{- if ne $sni \"\" }}, sni: {{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify: true{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}, fingerprint: {{ $proxy.Fingerprint }}{{- end }}{{- if or (eq $proxy.Transport \"ws\") (eq $proxy.Transport \"websocket\") }}, network: ws, ws-opts: { path: {{ default \"/\" $proxy.Path }}{{- if ne (default \"\" $proxy.Host) \"\" }}, headers: { Host: {{ $proxy.Host }} }{{- end }} }{{- else if eq $proxy.Transport \"http\" }}, network: http, http-opts: { method: GET, path: [{{ default \"/\" $proxy.Path | quote }}]{{- if ne (default \"\" $proxy.Host) \"\" }}, headers: { Host: [{{ $proxy.Host | quote }}] }{{- end }} }{{- else if eq $proxy.Transport \"grpc\" }}, network: grpc, grpc-opts: { grpc-service-name: {{ default \"grpc\" $proxy.ServiceName }} }{{- end }} }\n {{- else if or (eq $proxy.Type \"hysteria2\") (eq $proxy.Type \"hy2\") }}\n - { name: {{ $proxy.Name | quote }}, type: hysteria2, server: {{ $server }}, port: {{ $proxy.Port }}, password: {{ $password }}, {{ $common }}{{- if ne $sni \"\" }}, sni: {{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify: true{{- end }}{{- if ne (default \"\" $proxy.ObfsPassword) \"\" }}, obfs: salamander, obfs-password: {{ $proxy.ObfsPassword }}{{- end }}{{- if ne (default \"\" $proxy.HopPorts) \"\" }}, ports: {{ $proxy.HopPorts }}{{- end }}{{- if ne (default 0 $proxy.HopInterval) 0 }}, hop-interval: {{ $proxy.HopInterval }}{{- end }} }\n {{- else if eq $proxy.Type \"tuic\" }}\n - { name: {{ $proxy.Name | quote }}, type: tuic, server: {{ $server }}, port: {{ $proxy.Port }}, uuid: {{ default \"\" $proxy.ServerKey }}, password: {{ $password }}, {{ $common }}{{- if ne $sni \"\" }}, sni: {{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify: true{{- end }}{{- if $proxy.DisableSNI }}, disable-sni: true{{- end }}{{- if $proxy.ReduceRtt }}, reduce-rtt: true{{- end }}{{- if ne (default \"\" $proxy.UDPRelayMode) \"\" }}, udp-relay-mode: {{ $proxy.UDPRelayMode }}{{- end }}{{- if ne (default \"\" $proxy.CongestionController) \"\" }}, congestion-controller: {{ $proxy.CongestionController }}{{- end }} }\n {{- else if eq $proxy.Type \"wireguard\" }}\n - { name: {{ $proxy.Name | quote }}, type: wireguard, server: {{ $server }}, port: {{ $proxy.Port }}, private-key: {{ default \"\" $proxy.ServerKey }}, public-key: {{ default \"\" $proxy.RealityPublicKey }}, {{ $common }}{{- if ne (default \"\" $proxy.Path) \"\" }}, preshared-key: {{ $proxy.Path }}{{- end }}{{- if ne (default \"\" $proxy.RealityServerAddr) \"\" }}, ip: {{ $proxy.RealityServerAddr }}{{- end }}{{- if ne (default 0 $proxy.RealityServerPort) 0 }}, ipv6: {{ $proxy.RealityServerPort }}{{- end }} }\n {{- else if eq $proxy.Type \"anytls\" }}\n - { name: {{ $proxy.Name | quote }}, type: anytls, server: {{ $server }}, port: {{ $proxy.Port }}, password: {{ $password }}, {{ $common }} }\n {{- else }}\n - { name: {{ $proxy.Name | quote }}, type: {{ $proxy.Type }}, server: {{ $server }}, port: {{ $proxy.Port }}, {{ $common }} }\n {{- end }}\n{{- end }}\n\n{{- range $proxy := .Proxies }}\n {{- if not (or (eq $proxy.Type \"shadowsocks\") (eq $proxy.Type \"vmess\") (eq $proxy.Type \"vless\") (eq $proxy.Type \"trojan\") (eq $proxy.Type \"hysteria2\") (eq $proxy.Type \"tuic\")) }}\n# Skipped (unsupported by Clash): {{ $proxy.Name }} ({{ $proxy.Type }})\n {{- end }}\n{{- end }}\n\nproxy-groups:\n - { name: 🚀 Proxy, type: select, proxies: [🌏 Auto, 🎯 Direct, {{ $proxyNames }}] }\n - { name: 🍎 Apple, type: select, proxies: [🚀 Proxy, 🎯 Direct, {{ $proxyNames }}] }\n - { name: 🔍 Google, type: select, proxies: [🚀 Proxy, 🎯 Direct, {{ $proxyNames }}] }\n - { name: 🪟 Microsoft, type: select, proxies: [🚀 Proxy, 🎯 Direct, {{ $proxyNames }}] }\n - { name: 📺 GlobalMedia, type: select, proxies: [🚀 Proxy, 🎯 Direct, {{ $proxyNames }}] }\n - { name: 📟 Telegram, type: select, proxies: [🚀 Proxy, 🎯 Direct, {{ $proxyNames }}] }\n - { name: 🤖 AI, type: select, proxies: [🚀 Proxy, 🎯 Direct, {{ $proxyNames }}] }\n - { name: 🪙 Crypto, type: select, proxies: [🚀 Proxy, 🎯 Direct, {{ $proxyNames }}] }\n - { name: 🎮 Game, type: select, proxies: [🚀 Proxy, 🎯 Direct, {{ $proxyNames }}] }\n - { name: 🇨🇳 China, type: select, proxies: [🎯 Direct, 🚀 Proxy, {{ $proxyNames }}] }\n - { name: 🎯 Direct, type: select, proxies: [DIRECT], hidden: true }\n - { name: 🐠 Final, type: select, proxies: [🚀 Proxy, 🎯 Direct, {{ $proxyNames }}] }\n - { name: 🌏 Auto, type: url-test, proxies: [{{ $proxyNames }}] }\n\nrules:\n - RULE-SET, Apple, 🍎 Apple\n - RULE-SET, Google, 🔍 Google\n - RULE-SET, Microsoft, 🪟 Microsoft\n - RULE-SET, Github, 🪟 Microsoft\n - RULE-SET, HBO, 📺 GlobalMedia\n - RULE-SET, Disney, 📺 GlobalMedia\n - RULE-SET, TikTok, 📺 GlobalMedia\n - RULE-SET, Netflix, 📺 GlobalMedia\n - RULE-SET, GlobalMedia, 📺 GlobalMedia\n - RULE-SET, Telegram, 📟 Telegram\n - RULE-SET, OpenAI, 🤖 AI\n - RULE-SET, Gemini, 🤖 AI\n - RULE-SET, Copilot, 🤖 AI\n - RULE-SET, Claude, 🤖 AI\n - RULE-SET, Crypto, 🪙 Crypto\n - RULE-SET, Cryptocurrency, 🪙 Crypto\n - RULE-SET, Game, 🎮 Game\n - RULE-SET, Global, 🚀 Proxy\n - RULE-SET, ChinaMax, 🇨🇳 China\n - RULE-SET, Lan, 🎯 Direct\n - GEOIP, CN, 🇨🇳 China\n - MATCH, 🐠 Final\n\nrule-providers:\n Apple:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/Apple/Apple_Classical_No_Resolve.yaml\n interval: 86400\n Google:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/Google/Google_No_Resolve.yaml\n interval: 86400\n Microsoft:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/Microsoft/Microsoft.yaml\n interval: 86400\n Github:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/GitHub/GitHub.yaml\n interval: 86400\n HBO:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/HBO/HBO.yaml\n interval: 86400\n Disney:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/Disney/Disney.yaml\n interval: 86400\n TikTok:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/TikTok/TikTok.yaml\n interval: 86400\n Netflix:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/Netflix/Netflix.yaml\n interval: 86400\n GlobalMedia:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/GlobalMedia/GlobalMedia_Classical_No_Resolve.yaml\n interval: 86400\n Telegram:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/Telegram/Telegram_No_Resolve.yaml\n interval: 86400\n OpenAI:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/OpenAI/OpenAI.yaml\n interval: 86400\n Gemini:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/Gemini/Gemini.yaml\n interval: 86400\n Copilot:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/Copilot/Copilot.yaml\n interval: 86400\n Claude:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/Claude/Claude.yaml\n interval: 86400\n Crypto:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/Crypto/Crypto.yaml\n interval: 86400\n Cryptocurrency:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/Cryptocurrency/Cryptocurrency.yaml\n interval: 86400\n Game:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/Game/Game.yaml\n interval: 86400\n Global:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/Global/Global_Classical_No_Resolve.yaml\n interval: 86400\n ChinaMax:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/ChinaMax/ChinaMax_Classical_No_Resolve.yaml\n interval: 86400\n Lan:\n type: http\n behavior: classical\n format: yaml\n url: https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Clash/Lan/Lan.yaml\n interval: 86400\n\nurl-rewrite:\n - ^https?:\\/\\/(www.)?g\\.cn https://www.google.com 302\n - ^https?:\\/\\/(www.)?google\\.cn https://www.google.com 302\n', 'yaml', '{}', '2025-08-12 23:10:00.487', '2025-08-15 22:01:27.031'); +INSERT INTO `subscribe_application` (`id`, `name`, `icon`, `description`, `scheme`, `user_agent`, `is_default`, `subscribe_template`, `output_format`, `download_link`, `created_at`, `updated_at`) VALUES (4, 'SingBox', '', '', 'sing-box://import-remote-profile?url=${encodeURIComponent(url)}#${name}', 'sing-box', 0, '{{- $GiB := 1073741824.0 -}}\n{{- $used := printf \"%.2f\" (divf (add (.UserInfo.Download | default 0 | float64) (.UserInfo.Upload | default 0 | float64)) $GiB) -}}\n{{- $traffic := (.UserInfo.Traffic | default 0 | float64) -}}\n{{- $total := printf \"%.2f\" (divf $traffic $GiB) -}}\n\n{{- $exp := \"\" -}}\n{{- $expStr := printf \"%v\" .UserInfo.ExpiredAt -}}\n{{- if regexMatch `^[0-9]+$` $expStr -}}\n {{- $ts := $expStr | float64 -}}\n {{- $sec := ternary (divf $ts 1000.0) $ts (ge (len $expStr) 13) -}}\n {{- $exp = (date \"2006-01-02 15:04:05\" (unixEpoch ($sec | int64))) -}}\n{{- else -}}\n {{- $exp = $expStr -}}\n{{- end -}}\n\n{{- $supportedProxies := list -}}\n{{- range $proxy := .Proxies -}}\n {{- if or (eq $proxy.Type \"shadowsocks\") (eq $proxy.Type \"vmess\") (eq $proxy.Type \"vless\") (eq $proxy.Type \"trojan\") (eq $proxy.Type \"hysteria2\") (eq $proxy.Type \"hy2\") (eq $proxy.Type \"tuic\") -}}\n {{- $supportedProxies = append $supportedProxies $proxy -}}\n {{- end -}}\n{{- end -}}\n\n{{- $proxyNames := \"\" -}}\n{{- if gt (len $supportedProxies) 0 -}}\n {{- range $proxy := $supportedProxies -}}\n {{- if eq $proxyNames \"\" -}}\n {{- $proxyNames = printf \"\\\"%s\\\"\" $proxy.Name -}}\n {{- else -}}\n {{- $proxyNames = printf \"%s, \\\"%s\\\"\" $proxyNames $proxy.Name -}}\n {{- end -}}\n {{- end -}}\n {{- $proxyNames = printf \", %s\" $proxyNames -}}\n{{- end -}}\n\n\n{\n \"log\": {\"level\": \"info\", \"timestamp\": true},\n \"experimental\": {\n \"cache_file\": {\"enabled\": true, \"path\": \"cache.db\", \"cache_id\": \"my_profile\", \"store_fakeip\": false},\n \"clash_api\": {\"external_controller\": \"127.0.0.1:9090\", \"external_ui\": \"ui\", \"secret\": \"\", \"external_ui_download_url\": \"https://mirror.ghproxy.com/https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip\", \"external_ui_download_detour\": \"direct\", \"default_mode\": \"rule\"}\n },\n \"dns\": {\n \"servers\": [\n {\"tag\": \"dns_proxy\",\"address\": \"tls://8.8.8.8\",\"detour\": \"Proxy\"},\n {\"tag\": \"dns_direct\",\"address\": \"https://223.5.5.5/dns-query\",\"detour\": \"direct\"}\n ],\n \"rules\": [\n {\"rule_set\": \"geosite-cn\", \"server\": \"dns_direct\"},\n {\"clash_mode\": \"direct\", \"server\": \"dns_direct\"},\n {\"clash_mode\": \"global\", \"server\": \"dns_proxy\"},\n {\"rule_set\": \"geosite-geolocation-!cn\", \"server\": \"dns_proxy\"}\n ],\n \"final\": \"dns_direct\",\n \"strategy\": \"ipv4_only\"\n },\n \"inbounds\": [\n {\"tag\": \"tun-in\", \"type\": \"tun\", \"address\": [\"172.18.0.1/30\",\"fdfe:dcba:9876::1/126\"], \"auto_route\": true, \"strict_route\": true, \"stack\": \"system\",\n \"platform\": {\"http_proxy\": {\"enabled\": true, \"server\": \"127.0.0.1\", \"server_port\": 7890}}},\n {\"tag\": \"mixed-in\", \"type\": \"mixed\", \"listen\": \"127.0.0.1\", \"listen_port\": 7890}\n ],\n \"outbounds\": [\n {\"tag\": \"Proxy\", \"type\": \"selector\", \"outbounds\": [\"Auto - UrlTest\", \"direct\"{{ $proxyNames }}]},\n {\"tag\": \"Domestic\", \"type\": \"selector\", \"outbounds\": [\"direct\", \"Proxy\"{{ $proxyNames }}]},\n {\"tag\": \"Others\", \"type\": \"selector\", \"outbounds\": [\"Proxy\", \"direct\"{{ $proxyNames }}]},\n {\"tag\": \"AI Suite\", \"type\": \"selector\", \"outbounds\": [\"Proxy\", \"direct\"{{ $proxyNames }}]},\n {\"tag\": \"Netflix\", \"type\": \"selector\", \"outbounds\": [\"Proxy\", \"direct\"{{ $proxyNames }}]},\n {\"tag\": \"Disney Plus\", \"type\": \"selector\", \"outbounds\": [\"Proxy\", \"direct\"{{ $proxyNames }}]},\n {\"tag\": \"YouTube\", \"type\": \"selector\", \"outbounds\": [\"Proxy\", \"direct\"{{ $proxyNames }}]},\n {\"tag\": \"Max\", \"type\": \"selector\", \"outbounds\": [\"Proxy\", \"direct\"{{ $proxyNames }}]},\n {\"tag\": \"Spotify\", \"type\": \"selector\", \"outbounds\": [\"Proxy\", \"direct\"{{ $proxyNames }}]},\n {\"tag\": \"Apple\", \"type\": \"selector\", \"outbounds\": [\"direct\", \"Proxy\"{{ $proxyNames }}]},\n {\"tag\": \"Telegram\", \"type\": \"selector\", \"outbounds\": [\"Proxy\", \"direct\"{{ $proxyNames }}]},\n {\"tag\": \"Microsoft\", \"type\": \"selector\", \"outbounds\": [\"Proxy\", \"direct\"{{ $proxyNames }}]},\n {\"tag\": \"Tiktok\", \"type\": \"selector\", \"outbounds\": [\"Proxy\", \"direct\"{{ $proxyNames }}]},\n {\"tag\": \"AdBlock\", \"type\": \"selector\", \"outbounds\": [\"block\", \"direct\", \"Proxy\"]},\n {{- if gt (len $supportedProxies) 0 }}\n {\"tag\": \"Auto - UrlTest\", \"type\": \"urltest\", \"outbounds\": [{{ $proxyNames | trimPrefix \", \" }}], \"url\": \"http://cp.cloudflare.com/\", \"interval\": \"10m\", \"tolerance\": 50}\n {{- range $i, $proxy := $supportedProxies }},\n{{- $server := $proxy.Server -}}\n{{- if and (contains $proxy.Server \":\") (not (hasPrefix \"[\" $proxy.Server)) -}}\n {{- $server = printf \"[%s]\" $proxy.Server -}}\n{{- end -}}\n\n{{- $sni := default \"\" $proxy.SNI -}}\n{{- if eq $sni \"\" -}}\n {{- $sni = default \"\" $proxy.Host -}}\n{{- end -}}\n{{- if and (eq $sni \"\") (not (or (regexMatch \"^[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+$\" $proxy.Server) (contains $proxy.Server \":\"))) -}}\n {{- $sni = $proxy.Server -}}\n{{- end -}}\n\n{{- $password := $.UserInfo.Password -}}\n{{- if and (eq $proxy.Type \"shadowsocks\") (ne (default \"\" $proxy.ServerKey) \"\") -}}\n {{- $method := $proxy.Method -}}\n {{- if or (hasPrefix \"2022-blake3-\" $method) (eq $method \"2022-blake3-aes-128-gcm\") (eq $method \"2022-blake3-aes-256-gcm\") -}}\n {{- $userKeyLen := ternary 16 32 (hasSuffix \"128-gcm\" $method) -}}\n {{- $pwdStr := printf \"%s\" $password -}}\n {{- $userKey := ternary $pwdStr (trunc $userKeyLen $pwdStr) (le (len $pwdStr) $userKeyLen) -}}\n {{- $serverB64 := b64enc $proxy.ServerKey -}}\n {{- $userB64 := b64enc $userKey -}}\n {{- $password = printf \"%s:%s\" $serverB64 $userB64 -}}\n {{- end -}}\n{{- end -}}\n\n{{- $common := `\"tcp_fast_open\": true, \"udp_over_tcp\": false` -}}\n\n{{- if eq $proxy.Type \"shadowsocks\" -}}\n {{- $method := default \"aes-128-gcm\" $proxy.Method -}}\n { \"type\": \"shadowsocks\", \"tag\": {{ $proxy.Name | quote }}, \"server\": {{ $server | quote }}, \"server_port\": {{ $proxy.Port }}, \"method\": {{ $method | quote }}, \"password\": {{ $password | quote }}, {{ $common }} }\n{{- else if eq $proxy.Type \"trojan\" -}}\n { \"type\": \"trojan\", \"tag\": {{ $proxy.Name | quote }}, \"server\": {{ $server | quote }}, \"server_port\": {{ $proxy.Port }}, \"password\": {{ $password | quote }}{{- if or (eq $proxy.Transport \"ws\") (eq $proxy.Transport \"websocket\") }}, \"transport\": {\"type\": \"ws\", \"path\": {{ default \"/\" $proxy.Path | quote }}{{- if ne (default \"\" $proxy.Host) \"\" }}, \"headers\": {\"Host\": {{ $proxy.Host | quote }} }{{- end -}}}{{- else if eq $proxy.Transport \"grpc\" }}, \"transport\": {\"type\": \"grpc\", \"service_name\": {{ default \"grpc\" $proxy.ServiceName | quote }}}{{- end }}, {{ $common }}, \"tls\": {\"enabled\": true{{- if ne $sni \"\" }}, \"server_name\": {{ $sni | quote }}{{- end }}{{- if $proxy.AllowInsecure }}, \"insecure\": true{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}, \"utls\": {\"enabled\": true, \"fingerprint\": {{ $proxy.Fingerprint | quote }} }{{- end }}} }\n{{- else if eq $proxy.Type \"vless\" -}}\n { \"type\": \"vless\", \"tag\": {{ $proxy.Name | quote }}, \"server\": {{ $server | quote }}, \"server_port\": {{ $proxy.Port }}, \"uuid\": {{ $password | quote }}{{- if ne (default \"\" $proxy.Flow) \"\" }}, \"flow\": {{ $proxy.Flow | quote }}{{- end }}{{- if or (eq $proxy.Transport \"ws\") (eq $proxy.Transport \"websocket\") }}, \"transport\": {\"type\": \"ws\", \"path\": {{ default \"/\" $proxy.Path | quote }}{{- if ne (default \"\" $proxy.Host) \"\" }}, \"headers\": {\"Host\": {{ $proxy.Host | quote }} }{{- end -}}}{{- else if eq $proxy.Transport \"grpc\" }}, \"transport\": {\"type\": \"grpc\", \"service_name\": {{ default \"grpc\" $proxy.ServiceName | quote }}}{{- end }}, {{ $common }}{{- if ne (default \"\" $proxy.RealityPublicKey) \"\" }}, \"reality\": { \"enabled\": true, \"public_key\": {{ $proxy.RealityPublicKey | quote }}{{- if ne (default \"\" $proxy.RealityShortId) \"\" }}, \"short_id\": {{ $proxy.RealityShortId | quote }}{{- end }}{{- if ne $sni \"\" }}, \"server_name\": {{ $sni | quote }}{{- end }} }{{- else if or (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) (ne $sni \"\") $proxy.AllowInsecure (ne (default \"\" $proxy.Fingerprint) \"\") }}, \"tls\": {\"enabled\": true{{- if ne $sni \"\" }}, \"server_name\": {{ $sni | quote }}{{- end }}{{- if $proxy.AllowInsecure }}, \"insecure\": true{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}, \"utls\": {\"enabled\": true, \"fingerprint\": {{ $proxy.Fingerprint | quote }} }{{- end }}}{{- end }} }\n{{- else if eq $proxy.Type \"vmess\" -}}\n { \"type\": \"vmess\", \"tag\": {{ $proxy.Name | quote }}, \"server\": {{ $server | quote }}, \"server_port\": {{ $proxy.Port }}, \"uuid\": {{ $password | quote }}, \"security\": \"auto\", {{ $common }}{{- if or (eq $proxy.Transport \"ws\") (eq $proxy.Transport \"websocket\") }}, \"transport\": {\"type\": \"ws\", \"path\": {{ default \"/\" $proxy.Path | quote }}{{- if ne (default \"\" $proxy.Host) \"\" }}, \"headers\": {\"Host\": {{ $proxy.Host | quote }} }{{- end -}}}{{- else if eq $proxy.Transport \"grpc\" }}, \"transport\": {\"type\": \"grpc\", \"service_name\": {{ default \"grpc\" $proxy.ServiceName | quote }}}{{- end }}{{- if or (or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\")) (ne $sni \"\") $proxy.AllowInsecure (ne (default \"\" $proxy.Fingerprint) \"\") }}, \"tls\": {\"enabled\": true{{- if ne $sni \"\" }}, \"server_name\": {{ $sni | quote }}{{- end }}{{- if $proxy.AllowInsecure }}, \"insecure\": true{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}, \"utls\": {\"enabled\": true, \"fingerprint\": {{ $proxy.Fingerprint | quote }} }{{- end }}}{{- end }} }\n{{- else if or (eq $proxy.Type \"hysteria2\") (eq $proxy.Type \"hy2\") -}}\n { \"type\": \"hysteria2\", \"tag\": {{ $proxy.Name | quote }}, \"server\": {{ $server | quote }}, \"server_port\": {{ $proxy.Port }}, \"password\": {{ $password | quote }}{{- if ne (default \"\" $proxy.ObfsPassword) \"\" }}, \"obfs\": { \"type\": \"salamander\", \"password\": {{ $proxy.ObfsPassword | quote }} }{{- end }}{{- if ne (default \"\" $proxy.HopPorts) \"\" }}, \"ports\": {{ $proxy.HopPorts | quote }}{{- end }}{{- if ne (default 0 $proxy.HopInterval) 0 }}, \"hop_interval\": {{ $proxy.HopInterval }}{{- end }}, {{ $common }}, \"tls\": {\"enabled\": true{{- if ne $sni \"\" }}, \"server_name\": {{ $sni | quote }}{{- end }}{{- if $proxy.AllowInsecure }}, \"insecure\": true{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}, \"utls\": {\"enabled\": true, \"fingerprint\": {{ $proxy.Fingerprint | quote }} }{{- end }}} }\n{{- else if eq $proxy.Type \"tuic\" -}}\n { \"type\": \"tuic\", \"tag\": {{ $proxy.Name | quote }}, \"server\": {{ $server | quote }}, \"server_port\": {{ $proxy.Port }}, \"uuid\": {{ default \"\" $proxy.ServerKey | quote }}, \"password\": {{ $password | quote }}{{- if $proxy.DisableSNI }}, \"disable_sni\": true{{- end }}{{- if $proxy.ReduceRtt }}, \"reduce_rtt\": true{{- end }}{{- if ne (default \"\" $proxy.UDPRelayMode) \"\" }}, \"udp_relay_mode\": {{ $proxy.UDPRelayMode | quote }}{{- end }}{{- if ne (default \"\" $proxy.CongestionController) \"\" }}, \"congestion_control\": {{ $proxy.CongestionController | quote }}{{- end }}, {{ $common }}, \"alpn\": [\"h3\"], \"tls\": {\"enabled\": true{{- if ne $sni \"\" }}, \"server_name\": {{ $sni | quote }}{{- end }}{{- if $proxy.AllowInsecure }}, \"insecure\": true{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}, \"utls\": {\"enabled\": true, \"fingerprint\": {{ $proxy.Fingerprint | quote }} }{{- end }}} }\n{{- else if eq $proxy.Type \"anytls\" -}}\n { \"type\": \"anytls\", \"tag\": {{ $proxy.Name | quote }}, \"server\": {{ $server | quote }}, \"server_port\": {{ $proxy.Port }}, \"password\": {{ $password | quote }}, {{ $common }}, \"tls\": {\"enabled\": true{{- if ne $sni \"\" }}, \"server_name\": {{ $sni | quote }}{{- end }}{{- if $proxy.AllowInsecure }}, \"insecure\": true{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}, \"utls\": {\"enabled\": true, \"fingerprint\": {{ $proxy.Fingerprint | quote }} }{{- end }}} }\n{{- else if eq $proxy.Type \"wireguard\" -}}\n { \"type\": \"wireguard\", \"tag\": {{ $proxy.Name | quote }}, \"server\": {{ $server | quote }}, \"server_port\": {{ $proxy.Port }}, \"private_key\": {{ default \"\" $proxy.ServerKey | quote }}, \"peer_public_key\": {{ default \"\" $proxy.RealityPublicKey | quote }}{{- if ne (default \"\" $proxy.Path) \"\" }}, \"pre_shared_key\": {{ $proxy.Path | quote }}{{- end }}{{- if ne (default \"\" $proxy.RealityServerAddr) \"\" }}, \"local_address\": [{{ $proxy.RealityServerAddr | quote }}]{{- end }}, {{ $common }} }\n{{- else -}}\n { \"type\": \"direct\", \"tag\": {{ $proxy.Name | quote }}, {{ $common }} }\n{{- end }}\n {{- end }},\n {{- end }}\n {\"type\": \"direct\", \"tag\": \"direct\"},\n {\"type\": \"block\", \"tag\": \"block\"}\n ],\n \"route\": {\n \"auto_detect_interface\": true, \"final\": \"Proxy\",\n \"rules\": [\n {\"type\": \"logical\", \"mode\": \"or\", \"rules\": [{\"port\": 53},{\"protocol\": \"dns\"}], \"action\": \"hijack-dns\"},\n {\"rule_set\": \"geosite-category-ads-all\", \"outbound\": \"AdBlock\"},\n {\"clash_mode\": \"direct\", \"outbound\": \"direct\"},\n {\"clash_mode\": \"global\", \"outbound\": \"Proxy\"},\n {\"domain\": [\"clash.razord.top\",\"yacd.metacubex.one\",\"yacd.haishan.me\",\"d.metacubex.one\"], \"outbound\": \"direct\"},\n {\"ip_is_private\": true, \"outbound\": \"direct\"},\n {\"rule_set\": [\"geoip-netflix\",\"geosite-netflix\"], \"outbound\": \"Netflix\"},\n {\"rule_set\": \"geosite-disney\", \"outbound\": \"Disney Plus\"},\n {\"rule_set\": \"geosite-youtube\", \"outbound\": \"YouTube\"},\n {\"rule_set\": \"geosite-max\", \"outbound\": \"Max\"},\n {\"rule_set\": \"geosite-spotify\", \"outbound\": \"Spotify\"},\n {\"rule_set\": [\"geoip-apple\",\"geosite-apple\"], \"outbound\": \"Apple\"},\n {\"rule_set\": [\"geoip-telegram\",\"geosite-telegram\"], \"outbound\": \"Telegram\"},\n {\"rule_set\": \"geosite-openai\", \"outbound\": \"AI Suite\"},\n {\"rule_set\": \"geosite-microsoft\", \"outbound\": \"Microsoft\"},\n {\"rule_set\": \"geosite-tiktok\", \"outbound\": \"Tiktok\"},\n {\"rule_set\": \"geosite-private\", \"outbound\": \"direct\"},\n {\"rule_set\": [\"geoip-cn\",\"geosite-cn\"], \"outbound\": \"Domestic\"},\n {\"rule_set\": \"geosite-geolocation-!cn\", \"outbound\": \"Others\"}\n ],\n \"rule_set\": [\n {\"tag\": \"geoip-cn\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geoip/cn.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geosite-cn\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/cn.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geosite-private\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/private.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geosite-geolocation-!cn\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/geolocation-!cn.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geosite-category-ads-all\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/category-ads-all.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geoip-netflix\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geoip/netflix.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geosite-netflix\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/netflix.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geosite-disney\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/disney.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geosite-youtube\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/youtube.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geosite-max\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/hbomax.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geosite-spotify\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/spotify.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geoip-apple\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo-lite/geoip/apple.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geosite-apple\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/apple.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geoip-telegram\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geoip/telegram.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geosite-telegram\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/telegram.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geosite-openai\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/openai.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geosite-microsoft\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/microsoft.srs\",\"download_detour\": \"direct\"},\n {\"tag\": \"geosite-tiktok\",\"type\": \"remote\",\"format\": \"binary\",\"url\": \"https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/tiktok.srs\",\"download_detour\": \"direct\"}\n ]\n }\n}\n', 'json', '{}', '2025-08-12 23:30:10.016', '2025-08-15 22:01:10.801'); +INSERT INTO `subscribe_application` (`id`, `name`, `icon`, `description`, `scheme`, `user_agent`, `is_default`, `subscribe_template`, `output_format`, `download_link`, `created_at`, `updated_at`) VALUES (5, 'Surge', '', '', 'surge:///install-config?url=${encodeURIComponent(url)}', 'Surge', 0, '{{- $GiB := 1073741824.0 -}}\n{{- $used := printf \"%.2f\" (divf (add (.UserInfo.Download | default 0 | float64) (.UserInfo.Upload | default 0 | float64)) $GiB) -}}\n{{- $traffic := (.UserInfo.Traffic | default 0 | float64) -}}\n{{- $total := printf \"%.2f\" (divf $traffic $GiB) -}}\n\n{{- $exp := \"\" -}}\n{{- $expStr := printf \"%v\" .UserInfo.ExpiredAt -}}\n{{- if regexMatch `^[0-9]+$` $expStr -}}\n {{- $ts := $expStr | float64 -}}\n {{- $sec := ternary (divf $ts 1000.0) $ts (ge (len $expStr) 13) -}}\n {{- $exp = (date \"2006-01-02 15:04:05\" (unixEpoch ($sec | int64))) -}}\n{{- else -}}\n {{- $exp = $expStr -}}\n{{- end -}}\n\n{{- $supportedProxies := list -}}\n{{- range $proxy := .Proxies -}}\n {{- if or (eq $proxy.Type \"shadowsocks\") (eq $proxy.Type \"vmess\") (eq $proxy.Type \"trojan\") (eq $proxy.Type \"hysteria2\") (eq $proxy.Type \"tuic\") -}}\n {{- $supportedProxies = append $supportedProxies $proxy -}}\n {{- end -}}\n{{- end -}}\n\n{{- $proxyNames := \"\" -}}\n{{- range $proxy := $supportedProxies -}}\n {{- if eq $proxyNames \"\" -}}\n {{- $proxyNames = $proxy.Name -}}\n {{- else -}}\n {{- $proxyNames = printf \"%s, %s\" $proxyNames $proxy.Name -}}\n {{- end -}}\n{{- end -}}\n\n#!MANAGED-CONFIG {{ .UserInfo.SubscribeURL }} interval=86400\n\n[General]\nloglevel = notify\nexternal-controller-access = perlnk@0.0.0.0:6170\nexclude-simple-hostnames = true\nshow-error-page-for-reject = true\nudp-priority = true\nudp-policy-not-supported-behaviour = reject\nipv6 = true\nipv6-vif = auto\nproxy-test-url = http://www.gstatic.com/generate_204\ninternet-test-url = http://connectivitycheck.platform.hicloud.com/generate_204\ntest-timeout = 5\ndns-server = system, 119.29.29.29, 223.5.5.5\nencrypted-dns-server = https://dns.alidns.com/dns-query\nhijack-dns = 8.8.8.8:53, 8.8.4.4:53, 1.1.1.1:53, 1.0.0.1:53\nskip-proxy = 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, 127.0.0.0/8, localhost, *.local\nalways-real-ip = *.lan, lens.l.google.com, *.srv.nintendo.net, *.stun.playstation.net, *.xboxlive.com, xbox.*.*.microsoft.com, *.msftncsi.com, *.msftconnecttest.com\n\n# > Surge Mac Parameters\nhttp-listen = 0.0.0.0:6088\nsocks5-listen = 0.0.0.0:6089\n\n# > Surge iOS Parameters\nallow-wifi-access = true\nallow-hotspot-access = true\nwifi-access-http-port = 6088\nwifi-access-socks5-port = 6089\n\n[Panel]\nSubscribeInfo = title={{ .SiteName }} - {{ .SubscribeName }}, content=官方网站: perlnk.com \\n已用流量: {{ $used }} GiB/{{ $total }} GiB \\n到期时间: {{ $exp }}, style=info\n\n[Proxy]\n{{- range $proxy := $supportedProxies }}\n {{- $server := $proxy.Server -}}\n {{- if and (contains $proxy.Server \":\") (not (hasPrefix \"[\" $proxy.Server)) -}}\n {{- $server = printf \"[%s]\" $proxy.Server -}}\n {{- end -}}\n\n {{- $sni := default \"\" $proxy.SNI -}}\n {{- if eq $sni \"\" -}}\n {{- $sni = default \"\" $proxy.Host -}}\n {{- end -}}\n {{- if and (eq $sni \"\") (not (or (regexMatch \"^[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+$\" $proxy.Server) (contains $proxy.Server \":\"))) -}}\n {{- $sni = $proxy.Server -}}\n {{- end -}}\n\n {{- $password := $.UserInfo.Password -}}\n {{- if and (eq $proxy.Type \"shadowsocks\") (ne (default \"\" $proxy.ServerKey) \"\") -}}\n {{- $method := $proxy.Method -}}\n {{- if or (hasPrefix \"2022-blake3-\" $method) (eq $method \"2022-blake3-aes-128-gcm\") (eq $method \"2022-blake3-aes-256-gcm\") -}}\n {{- $userKeyLen := ternary 16 32 (hasSuffix \"128-gcm\" $method) -}}\n {{- $pwdStr := printf \"%s\" $password -}}\n {{- $userKey := ternary $pwdStr (trunc $userKeyLen $pwdStr) (le (len $pwdStr) $userKeyLen) -}}\n {{- $serverB64 := b64enc $proxy.ServerKey -}}\n {{- $userB64 := b64enc $userKey -}}\n {{- $password = printf \"%s:%s\" $serverB64 $userB64 -}}\n {{- end -}}\n {{- end -}}\n\n {{- $common := \"udp-relay=true, tfo=true\" -}}\n\n {{- if eq $proxy.Type \"shadowsocks\" }}\n{{ $proxy.Name }} = ss, {{ $server }}, {{ $proxy.Port }}, encrypt-method={{ default \"aes-128-gcm\" $proxy.Method }}, password={{ $password }}{{- if ne (default \"\" $proxy.Transport) \"\" }}, obfs={{ $proxy.Transport }}, obfs-host={{ $sni }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"vmess\" }}\n{{ $proxy.Name }} = vmess, {{ $server }}, {{ $proxy.Port }}, username={{ $password }}{{- if or (eq $proxy.Transport \"ws\") (eq $proxy.Transport \"websocket\") }}, ws=true{{- if ne (default \"\" $proxy.Path) \"\" }}, ws-path={{ $proxy.Path }}{{- end }}{{- if ne (default \"\" $proxy.Host) \"\" }}, ws-headers=\"Host:{{ $proxy.Host }}\"{{- end }}{{- else if eq $proxy.Transport \"grpc\" }}, grpc=true{{- if ne (default \"\" $proxy.ServiceName) \"\" }}, grpc-service-name={{ $proxy.ServiceName }}{{- end }}{{- end }}{{- if or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\") }}, tls=true{{- end }}{{- if ne $sni \"\" }}, sni={{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}, fingerprint={{ $proxy.Fingerprint }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"vless\" }}\n{{ $proxy.Name }} = vless, {{ $server }}, {{ $proxy.Port }}, username={{ $password }}{{- if or (eq $proxy.Transport \"ws\") (eq $proxy.Transport \"websocket\") }}, ws=true{{- if ne (default \"\" $proxy.Path) \"\" }}, ws-path={{ $proxy.Path }}{{- end }}{{- if ne (default \"\" $proxy.Host) \"\" }}, ws-headers=\"Host:{{ $proxy.Host }}\"{{- end }}{{- else if eq $proxy.Transport \"grpc\" }}, grpc=true{{- if ne (default \"\" $proxy.ServiceName) \"\" }}, grpc-service-name={{ $proxy.ServiceName }}{{- end }}{{- end }}{{- if ne $sni \"\" }}, sni={{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}{{- if ne (default \"\" $proxy.Flow) \"\" }}, flow={{ $proxy.Flow }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"trojan\" }}\n{{ $proxy.Name }} = trojan, {{ $server }}, {{ $proxy.Port }}, password={{ $password }}{{- if or (eq $proxy.Transport \"ws\") (eq $proxy.Transport \"websocket\") }}, ws=true{{- if ne (default \"\" $proxy.Path) \"\" }}, ws-path={{ $proxy.Path }}{{- end }}{{- if ne (default \"\" $proxy.Host) \"\" }}, ws-headers=\"Host:{{ $proxy.Host }}\"{{- end }}{{- else if eq $proxy.Transport \"grpc\" }}, grpc=true{{- if ne (default \"\" $proxy.ServiceName) \"\" }}, grpc-service-name={{ $proxy.ServiceName }}{{- end }}{{- end }}{{- if ne $sni \"\" }}, sni={{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}, fingerprint={{ $proxy.Fingerprint }}{{- end }}, {{ $common }}\n {{- else if or (eq $proxy.Type \"hysteria2\") (eq $proxy.Type \"hy2\") }}\n{{ $proxy.Name }} = hysteria2, {{ $server }}, {{ $proxy.Port }}, password={{ $password }}{{- if ne $sni \"\" }}, sni={{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}{{- if ne (default \"\" $proxy.ObfsPassword) \"\" }}, obfs=salamander, obfs-password={{ $proxy.ObfsPassword }}{{- end }}{{- if ne (default \"\" $proxy.HopPorts) \"\" }}, ports={{ $proxy.HopPorts }}{{- end }}{{- if ne (default 0 $proxy.HopInterval) 0 }}, hop-interval={{ $proxy.HopInterval }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"tuic\" }}\n{{ $proxy.Name }} = tuic, {{ $server }}, {{ $proxy.Port }}, uuid={{ default \"\" $proxy.ServerKey }}, password={{ $password }}{{- if ne $sni \"\" }}, sni={{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}{{- if $proxy.DisableSNI }}, disable-sni=true{{- end }}{{- if $proxy.ReduceRtt }}, reduce-rtt=true{{- end }}{{- if ne (default \"\" $proxy.UDPRelayMode) \"\" }}, udp-relay-mode={{ $proxy.UDPRelayMode }}{{- end }}{{- if ne (default \"\" $proxy.CongestionController) \"\" }}, congestion-controller={{ $proxy.CongestionController }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"wireguard\" }}\n{{ $proxy.Name }} = wireguard, {{ $server }}, {{ $proxy.Port }}, private-key={{ default \"\" $proxy.ServerKey }}, public-key={{ default \"\" $proxy.RealityPublicKey }}{{- if ne (default \"\" $proxy.Path) \"\" }}, preshared-key={{ $proxy.Path }}{{- end }}{{- if ne (default \"\" $proxy.RealityServerAddr) \"\" }}, ip={{ $proxy.RealityServerAddr }}{{- end }}{{- if ne (default 0 $proxy.RealityServerPort) 0 }}, ipv6={{ $proxy.RealityServerPort }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"anytls\" }}\n{{ $proxy.Name }} = anytls, {{ $server }}, {{ $proxy.Port }}, password={{ $password }}{{- if ne $sni \"\" }}, sni={{ $sni }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}, {{ $common }}\n {{- else }}\n{{ $proxy.Name }} = {{ $proxy.Type }}, {{ $server }}, {{ $proxy.Port }}, {{ $common }}\n {{- end }}\n{{- end }}\n\n[Proxy Group]\n🚀 Proxy = select, 🌏 Auto, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🍎 Apple = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🔍 Google = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🪟 Microsoft = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n📺 GlobalMedia = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🤖 AI = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🪙 Crypto = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🎮 Game = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n📟 Telegram = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🇨🇳 China = select, 🎯 Direct, 🚀 Proxy, include-other-group=🇺🇳 Nodes\n🐠 Final = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🌏 Auto = smart, include-other-group=🇺🇳 Nodes\n🎯 Direct = select, DIRECT, hidden=1\n🇺🇳 Nodes = select, {{ $proxyNames }}, hidden=1\n\n[Rule]\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/Apple/Apple_All.list, 🍎 Apple\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/Google/Google.list, 🔍 Google\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/GitHub/GitHub.list, 🪟 Microsoft\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/Microsoft/Microsoft.list, 🪟 Microsoft\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/HBO/HBO.list, 📺 GlobalMedia\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/Disney/Disney.list, 📺 GlobalMedia\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/TikTok/TikTok.list, 📺 GlobalMedia\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/Netflix/Netflix.list, 📺 GlobalMedia\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/GlobalMedia/GlobalMedia_All_No_Resolve.list, 📺 GlobalMedia\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/Telegram/Telegram.list, 📟 Telegram\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/OpenAI/OpenAI.list, 🤖 AI\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/Gemini/Gemini.list, 🤖 AI\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/Copilot/Copilot.list, 🤖 AI\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/Claude/Claude.list, 🤖 AI\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/Crypto/Crypto.list, 🪙 Crypto\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/Cryptocurrency/Cryptocurrency.list, 🪙 Crypto\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/Game/Game.list, 🎮 Game\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/Global/Global_All_No_Resolve.list, 🚀 Proxy\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/ChinaMax/ChinaMax_All_No_Resolve.list, 🇨🇳 China\nRULE-SET, https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/refs/heads/master/rule/Surge/Lan/Lan.list, 🎯 Direct\n\nGEOIP, CN, 🇨🇳 China\nFINAL, 🐠 Final, dns-failed\n\n[URL Rewrite]\n^https?:\\/\\/(www.)?g\\.cn https://www.google.com 302\n^https?:\\/\\/(www.)?google\\.cn https://www.google.com 302\n\n{{- range $proxy := $supportedProxies }}\n {{- if not (or (eq $proxy.Type \"shadowsocks\") (eq $proxy.Type \"vmess\") (eq $proxy.Type \"trojan\") (eq $proxy.Type \"hysteria2\") (eq $proxy.Type \"tuic\")) }}\n# Skipped (unsupported by Surge): {{ $proxy.Name }} ({{ $proxy.Type }})\n {{- end }}\n{{- end }}', 'conf', '{}', '2025-08-13 00:12:37.809', '2025-08-15 22:00:50.528'); +COMMIT; diff --git a/initialize/migrate/database/02102_subscribe_config.down.sql b/initialize/migrate/database/02102_subscribe_config.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/initialize/migrate/database/02102_subscribe_config.up.sql b/initialize/migrate/database/02102_subscribe_config.up.sql new file mode 100644 index 0000000..4460100 --- /dev/null +++ b/initialize/migrate/database/02102_subscribe_config.up.sql @@ -0,0 +1,4 @@ +INSERT IGNORE INTO `system` (`id`, `category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`) +VALUES + (42, 'subscribe', 'UserAgentLimit', 'false', 'bool', 'User Agent Limit', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637'), + (43, 'subscribe', 'UserAgentList', '', 'string', 'User Agent List', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'); \ No newline at end of file diff --git a/initialize/migrate/database/02103_delete_application.down.sql b/initialize/migrate/database/02103_delete_application.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/initialize/migrate/database/02103_delete_application.up.sql b/initialize/migrate/database/02103_delete_application.up.sql new file mode 100644 index 0000000..1a3a778 --- /dev/null +++ b/initialize/migrate/database/02103_delete_application.up.sql @@ -0,0 +1,3 @@ +DROP TABLE IF EXISTS `application`; +DROP TABLE IF EXISTS `application_version`; +DROP TABLE IF EXISTS `application_config`; \ No newline at end of file diff --git a/initialize/migrate/database/02104_system_log.down.sql b/initialize/migrate/database/02104_system_log.down.sql new file mode 100644 index 0000000..2682c97 --- /dev/null +++ b/initialize/migrate/database/02104_system_log.down.sql @@ -0,0 +1,106 @@ +CREATE TABLE IF NOT EXISTS `user_balance_log` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL COMMENT 'User ID', + `amount` bigint NOT NULL COMMENT 'Amount', + `type` tinyint(1) NOT NULL COMMENT 'Type: 1: Recharge 2: Withdraw 3: Payment 4: Refund 5: Reward', + `order_id` bigint DEFAULT NULL COMMENT 'Order ID', + `balance` bigint NOT NULL COMMENT 'Balance', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`) + ) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `user_commission_log` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL COMMENT 'User ID', + `order_no` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Order No.', + `amount` bigint NOT NULL COMMENT 'Amount', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`) + ) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `user_gift_amount_log` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL COMMENT 'User ID', + `user_subscribe_id` bigint DEFAULT NULL COMMENT 'Deduction User Subscribe ID', + `order_no` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Order No.', + `type` tinyint(1) NOT NULL COMMENT 'Type: 1: Increase 2: Reduce', + `amount` bigint NOT NULL COMMENT 'Amount', + `balance` bigint NOT NULL COMMENT 'Balance', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Remark', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`) + ) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `user_login_log` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL COMMENT 'User ID', + `login_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Login IP', + `user_agent` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'UserAgent', + `success` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Login Success', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`) + ) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `user_reset_subscribe_log` +( + `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `user_id` BIGINT NOT NULL COMMENT 'User ID', + `type` TINYINT(1) NOT NULL COMMENT 'Type: 1: Auto 2: Advance 3: Paid', + `order_no` VARCHAR(255) DEFAULT NULL COMMENT 'Order No.', + `user_subscribe_id` BIGINT NOT NULL COMMENT 'User Subscribe ID', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation Time', + INDEX `idx_user_id` (`user_id`), + INDEX `idx_user_subscribe_id` (`user_subscribe_id`) + ) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `user_subscribe_log` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL COMMENT 'User ID', + `user_subscribe_id` bigint NOT NULL COMMENT 'User Subscribe ID', + `token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Token', + `ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'IP', + `user_agent` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'UserAgent', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_user_subscribe_id` (`user_subscribe_id`) + ) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `message_log` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'email' COMMENT 'Message Type', + `platform` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'smtp' COMMENT 'Platform', + `to` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'To', + `subject` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subject', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Content', + `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Status', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`) + ) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +DROP TABLE IF EXISTS `system_logs`; \ No newline at end of file diff --git a/initialize/migrate/database/02104_system_log.up.sql b/initialize/migrate/database/02104_system_log.up.sql new file mode 100644 index 0000000..b518e68 --- /dev/null +++ b/initialize/migrate/database/02104_system_log.up.sql @@ -0,0 +1,19 @@ +DROP TABLE IF EXISTS `user_balance_log`; +DROP TABLE IF EXISTS `user_commission_log`; +DROP TABLE IF EXISTS `user_gift_amount_log`; +DROP TABLE IF EXISTS `user_login_log`; +DROP TABLE IF EXISTS `user_reset_subscribe_log`; +DROP TABLE IF EXISTS `user_subscribe_log`; +DROP TABLE IF EXISTS `message_log`; +DROP TABLE IF EXISTS `system_logs`; +CREATE TABLE `system_logs` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `type` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Log Type: 1: Email Message 2: Mobile Message 3: Subscribe 4: Subscribe Traffic 5: Server Traffic 6: Login 7: Register 8: Balance 9: Commission 10: Reset Subscribe 11: Gift', + `date` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Log Date', + `object_id` bigint NOT NULL DEFAULT '0' COMMENT 'Object ID', + `content` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Log Content', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', + PRIMARY KEY (`id`), + KEY `idx_type` (`type`), + KEY `idx_object_id` (`object_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; \ No newline at end of file diff --git a/initialize/migrate/database/02105_node.down.sql b/initialize/migrate/database/02105_node.down.sql new file mode 100644 index 0000000..210462e --- /dev/null +++ b/initialize/migrate/database/02105_node.down.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS `nodes`; +DROP TABLE IF EXISTS `servers`; diff --git a/initialize/migrate/database/02105_node.up.sql b/initialize/migrate/database/02105_node.up.sql new file mode 100644 index 0000000..c9c310b --- /dev/null +++ b/initialize/migrate/database/02105_node.up.sql @@ -0,0 +1,28 @@ +CREATE TABLE IF NOT EXISTS `servers` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Server Name', + `country` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Country', + `city` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'City', + `ratio` decimal(4,2) NOT NULL DEFAULT '0.00' COMMENT 'Traffic Ratio', + `address` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Server Address', + `sort` bigint NOT NULL DEFAULT '0' COMMENT 'Sort', + `protocols` text COLLATE utf8mb4_general_ci COMMENT 'Protocol', + `last_reported_at` datetime(3) DEFAULT NULL COMMENT 'Last Reported Time', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +CREATE TABLE IF NOT EXISTS `nodes` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Node Name', + `tags` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Tags', + `port` smallint unsigned NOT NULL DEFAULT '0' COMMENT 'Connect Port', + `address` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Connect Address', + `server_id` bigint NOT NULL DEFAULT '0' COMMENT 'Server ID', + `protocol` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Protocol', + `enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Enabled', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; diff --git a/initialize/migrate/database/02106_subscribe.down.sql b/initialize/migrate/database/02106_subscribe.down.sql new file mode 100644 index 0000000..20984b7 --- /dev/null +++ b/initialize/migrate/database/02106_subscribe.down.sql @@ -0,0 +1,5 @@ +ALTER TABLE `subscribe` +DROP COLUMN `nodes`, + DROP COLUMN `node_tags`, + ADD COLUMN `server` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Server', + ADD COLUMN `server_group` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Server Group'; diff --git a/initialize/migrate/database/02106_subscribe.up.sql b/initialize/migrate/database/02106_subscribe.up.sql new file mode 100644 index 0000000..28f5db5 --- /dev/null +++ b/initialize/migrate/database/02106_subscribe.up.sql @@ -0,0 +1,7 @@ +ALTER TABLE `subscribe` +ADD COLUMN `nodes` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Node IDs', +ADD COLUMN `node_tags` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Node Tags', +DROP COLUMN `server`, +DROP COLUMN `server_group`; + +DROP TABLE IF EXISTS `server_rule_group`; diff --git a/initialize/migrate/database/02107_log_setting.down.sql b/initialize/migrate/database/02107_log_setting.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/initialize/migrate/database/02107_log_setting.up.sql b/initialize/migrate/database/02107_log_setting.up.sql new file mode 100644 index 0000000..c1bc8e2 --- /dev/null +++ b/initialize/migrate/database/02107_log_setting.up.sql @@ -0,0 +1,4 @@ +INSERT IGNORE INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`) +VALUES + ('log', 'AutoClear', 'true', 'bool', 'Auto Clear Log', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637'), + ('log', 'ClearDays', '7', 'int', 'Clear Days', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'); \ No newline at end of file diff --git a/initialize/migrate/database/02108_user_referral.down.sql b/initialize/migrate/database/02108_user_referral.down.sql new file mode 100644 index 0000000..3bdac5b --- /dev/null +++ b/initialize/migrate/database/02108_user_referral.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE `user` +DROP COLUMN `referral_percentage`, +DROP COLUMN `only_first_purchase`; \ No newline at end of file diff --git a/initialize/migrate/database/02108_user_referral.up.sql b/initialize/migrate/database/02108_user_referral.up.sql new file mode 100644 index 0000000..e50f765 --- /dev/null +++ b/initialize/migrate/database/02108_user_referral.up.sql @@ -0,0 +1,7 @@ +ALTER TABLE `user` + ADD COLUMN `referral_percentage` TINYINT UNSIGNED NOT NULL DEFAULT 0 + COMMENT 'Referral Percentage' + AFTER `commission`, + ADD COLUMN `only_first_purchase` TINYINT(1) NOT NULL DEFAULT 1 + COMMENT 'Only First Purchase' + AFTER `referral_percentage`; diff --git a/initialize/migrate/database/02109_node_sort.down.sql b/initialize/migrate/database/02109_node_sort.down.sql new file mode 100644 index 0000000..4413646 --- /dev/null +++ b/initialize/migrate/database/02109_node_sort.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE `nodes` +DROP COLUMN `sort`; \ No newline at end of file diff --git a/initialize/migrate/database/02109_node_sort.up.sql b/initialize/migrate/database/02109_node_sort.up.sql new file mode 100644 index 0000000..1a993d0 --- /dev/null +++ b/initialize/migrate/database/02109_node_sort.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE `nodes` + ADD COLUMN `sort` INT UNSIGNED NOT NULL DEFAULT 0 + COMMENT 'Sort' AFTER `enabled`; \ No newline at end of file diff --git a/initialize/migrate/database/02110_traffic_log_index.down.sql b/initialize/migrate/database/02110_traffic_log_index.down.sql new file mode 100644 index 0000000..f42807c --- /dev/null +++ b/initialize/migrate/database/02110_traffic_log_index.down.sql @@ -0,0 +1 @@ +DROP INDEX idx_traffic_log_time_user_sub ON traffic_log; diff --git a/initialize/migrate/database/02110_traffic_log_index.up.sql b/initialize/migrate/database/02110_traffic_log_index.up.sql new file mode 100644 index 0000000..2cf61f2 --- /dev/null +++ b/initialize/migrate/database/02110_traffic_log_index.up.sql @@ -0,0 +1 @@ +CREATE INDEX idx_traffic_log_time_user_sub ON traffic_log (timestamp, user_id, subscribe_id); diff --git a/initialize/migrate/database/02111_clear_table.down.sql b/initialize/migrate/database/02111_clear_table.down.sql new file mode 100644 index 0000000..85c9f3f --- /dev/null +++ b/initialize/migrate/database/02111_clear_table.down.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS `subscribe_type`; +DROP TABLE IF EXISTS `sms`; \ No newline at end of file diff --git a/initialize/migrate/database/02111_clear_table.up.sql b/initialize/migrate/database/02111_clear_table.up.sql new file mode 100644 index 0000000..85c9f3f --- /dev/null +++ b/initialize/migrate/database/02111_clear_table.up.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS `subscribe_type`; +DROP TABLE IF EXISTS `sms`; \ No newline at end of file diff --git a/initialize/migrate/database/02112_subscribe.down.sql b/initialize/migrate/database/02112_subscribe.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/initialize/migrate/database/02112_subscribe.up.sql b/initialize/migrate/database/02112_subscribe.up.sql new file mode 100644 index 0000000..1a79dbc --- /dev/null +++ b/initialize/migrate/database/02112_subscribe.up.sql @@ -0,0 +1,7 @@ +ALTER TABLE `subscribe` +DROP COLUMN `group_id`, +ADD COLUMN `language` VARCHAR(255) NOT NULL DEFAULT '' + COMMENT 'Language' + AFTER `name`; + +DROP TABLE IF EXISTS `subscribe_group`; \ No newline at end of file diff --git a/initialize/migrate/database/02113_task.down.sql b/initialize/migrate/database/02113_task.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/initialize/migrate/database/02113_task.up.sql b/initialize/migrate/database/02113_task.up.sql new file mode 100644 index 0000000..4e7b170 --- /dev/null +++ b/initialize/migrate/database/02113_task.up.sql @@ -0,0 +1,14 @@ +DROP TABLE IF EXISTS `email_task`; +CREATE TABLE `task` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID', + `type` tinyint NOT NULL COMMENT 'Task Type', + `scope` text COLLATE utf8mb4_general_ci COMMENT 'Task Scope', + `content` text COLLATE utf8mb4_general_ci COMMENT 'Task Content', + `status` tinyint NOT NULL DEFAULT '0' COMMENT 'Task Status: 0: Pending, 1: In Progress, 2: Completed, 3: Failed', + `errors` text COLLATE utf8mb4_general_ci COMMENT 'Task Errors', + `total` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'Total Number', + `current` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'Current Number', + `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', + `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; \ No newline at end of file diff --git a/initialize/migrate/database/02114_node_config.down.sql b/initialize/migrate/database/02114_node_config.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/initialize/migrate/database/02114_node_config.up.sql b/initialize/migrate/database/02114_node_config.up.sql new file mode 100644 index 0000000..1289647 --- /dev/null +++ b/initialize/migrate/database/02114_node_config.up.sql @@ -0,0 +1,8 @@ +INSERT +IGNORE INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`) +VALUE + ('server', 'TrafficReportThreshold', '0', 'int', 'Traffic report threshold', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'), + ('server', 'IPStrategy', '', 'string', 'IP Strategy', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'), + ('server', 'DNS', '', 'string', 'DNS', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'), + ('server', 'Block', '', 'string', 'Block', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'), + ('server', 'Outbound', '', 'string', 'Proxy Outbound', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'); \ No newline at end of file diff --git a/initialize/migrate/database/ppanel.sql b/initialize/migrate/database/ppanel.sql deleted file mode 100644 index f15cdf9..0000000 --- a/initialize/migrate/database/ppanel.sql +++ /dev/null @@ -1,562 +0,0 @@ -SET NAMES utf8mb4; -SET FOREIGN_KEY_CHECKS = 0; - --- ---------------------------- --- Table structure for ads --- ---------------------------- -CREATE TABLE IF NOT EXISTS `ads` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `title` varchar(255) COLLATE utf8mb4_german2_ci NOT NULL DEFAULT '' COMMENT 'Ads title', - `type` varchar(255) COLLATE utf8mb4_german2_ci NOT NULL DEFAULT '' COMMENT 'Ads type', - `content` text COLLATE utf8mb4_german2_ci COMMENT 'Ads content', - `target_url` varchar(512) COLLATE utf8mb4_german2_ci DEFAULT '' COMMENT 'Ads target url', - `start_time` datetime DEFAULT NULL COMMENT 'Ads start time', - `end_time` datetime DEFAULT NULL COMMENT 'Ads end time', - `status` tinyint(1) DEFAULT '0' COMMENT 'Ads status,0 disable,1 enable', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_german2_ci; - - --- ---------------------------- --- Table structure for announcement --- ---------------------------- -CREATE TABLE IF NOT EXISTS `announcement` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `title` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Title', - `content` text COLLATE utf8mb4_general_ci COMMENT 'Content', - `show` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Show', - `pinned` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Pinned', - `popup` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Popup', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; --- ---------------------------- --- Table structure for application --- ---------------------------- -CREATE TABLE IF NOT EXISTS `application` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用名称', - `icon` text COLLATE utf8mb4_general_ci NOT NULL COMMENT '应用图标', - `description` text COLLATE utf8mb4_general_ci COMMENT '更新描述', - `subscribe_type` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订阅类型', - `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间', - `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; --- ---------------------------- --- Table structure for application_config --- ---------------------------- -CREATE TABLE IF NOT EXISTS `application_config` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `app_id` bigint NOT NULL DEFAULT '0' COMMENT 'App id', - `encryption_key` text COLLATE utf8mb4_general_ci COMMENT 'Encryption Key', - `encryption_method` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Encryption Method', - `domains` text COLLATE utf8mb4_general_ci, - `startup_picture` text COLLATE utf8mb4_general_ci, - `startup_picture_skip_time` bigint NOT NULL DEFAULT '0' COMMENT 'Startup Picture Skip Time', - `invitation_link` text COLLATE utf8mb4_general_ci COMMENT 'Invitation Link', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; --- ---------------------------- --- Table structure for application_version --- ---------------------------- -CREATE TABLE IF NOT EXISTS `application_version` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `url` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用地址', - `version` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用版本', - `platform` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用平台', - `is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT '默认版本', - `description` text COLLATE utf8mb4_general_ci COMMENT '更新描述', - `application_id` bigint DEFAULT NULL COMMENT '所属应用', - `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间', - `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - KEY `fk_application_application_versions` (`application_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- ---------------------------- --- Table structure for coupon --- ---------------------------- -CREATE TABLE IF NOT EXISTS `coupon` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Coupon Name', - `code` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Coupon Code', - `count` bigint NOT NULL DEFAULT '0' COMMENT 'Count Limit', - `type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Coupon Type: 1: Percentage 2: Fixed Amount', - `discount` bigint NOT NULL DEFAULT '0' COMMENT 'Coupon Discount', - `start_time` bigint NOT NULL DEFAULT '0' COMMENT 'Start Time', - `expire_time` bigint NOT NULL DEFAULT '0' COMMENT 'Expire Time', - `user_limit` bigint NOT NULL DEFAULT '0' COMMENT 'User Limit', - `subscribe` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subscribe Limit', - `used_count` bigint NOT NULL DEFAULT '0' COMMENT 'Used Count', - `enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Enable', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`), - UNIQUE KEY `uni_coupon_code` (`code`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- ---------------------------- --- Table structure for document --- ---------------------------- -CREATE TABLE IF NOT EXISTS `document` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `title` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Document Title', - `content` text COLLATE utf8mb4_general_ci COMMENT 'Document Content', - `tags` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Document Tags', - `show` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Show', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; --- ---------------------------- --- Table structure for auth_method --- ---------------------------- -CREATE TABLE IF NOT EXISTS `auth_method` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `method` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'method', - `config` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'OAuth Configuration', - `enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Enabled', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`), - UNIQUE KEY `uni_auth_method` (`method`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- ---------------------------- --- Table structure for order --- ---------------------------- -CREATE TABLE IF NOT EXISTS `order` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `parent_id` bigint DEFAULT NULL COMMENT 'Parent Order Id', - `user_id` bigint NOT NULL DEFAULT '0' COMMENT 'User Id', - `order_no` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Order No', - `type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Order Type: 1: Subscribe, 2: Renewal, 3: ResetTraffic, 4: Recharge', - `quantity` bigint NOT NULL DEFAULT '1' COMMENT 'Quantity', - `price` bigint NOT NULL DEFAULT '0' COMMENT 'Original price', - `amount` bigint NOT NULL DEFAULT '0' COMMENT 'Order Amount', - `gift_amount` bigint NOT NULL DEFAULT '0' COMMENT 'User Gift Amount', - `discount` bigint NOT NULL DEFAULT '0' COMMENT 'Discount Amount', - `coupon` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Coupon', - `coupon_discount` bigint NOT NULL DEFAULT '0' COMMENT 'Coupon Discount Amount', - `commission` bigint NOT NULL DEFAULT '0' COMMENT 'Order Commission', - `payment_id` bigint NOT NULL DEFAULT '-1' COMMENT 'Payment Id', - `method` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Payment Method', - `fee_amount` bigint NOT NULL DEFAULT '0' COMMENT 'Fee Amount', - `trade_no` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Trade No', - `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Order Status: 1: Pending, 2: Paid, 3:Close, 4: Failed, 5:Finished', - `subscribe_id` bigint NOT NULL DEFAULT '0' COMMENT 'Subscribe Id', - `subscribe_token` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Renewal Subscribe Token', - `is_new` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is New Order', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`), - UNIQUE KEY `uni_order_order_no` (`order_no`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- ---------------------------- --- Table structure for payment --- ---------------------------- -CREATE TABLE IF NOT EXISTS `payment` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Payment Name', - `platform` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Payment Platform', - `description` text COLLATE utf8mb4_general_ci COMMENT 'Payment Description', - `icon` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Payment Icon', - `domain` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Notification Domain', - `config` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Payment Configuration', - `fee_mode` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Fee Mode: 0: No Fee 1: Percentage 2: Fixed Amount 3: Percentage + Fixed Amount', - `fee_percent` bigint DEFAULT '0' COMMENT 'Fee Percentage', - `fee_amount` bigint DEFAULT '0' COMMENT 'Fixed Fee Amount', - `enable` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Enabled', - `token` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Payment Token', - PRIMARY KEY (`id`), - UNIQUE KEY `uni_payment_token` (`token`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; --- ---------------------------- --- Table structure for server --- ---------------------------- -CREATE TABLE IF NOT EXISTS `server` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Node Name', - `tags` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Tags', - `country` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Country', - `city` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'City', - `latitude` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'latitude', - `longitude` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'longitude', - `server_addr` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Server Address', - `relay_mode` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'none' COMMENT 'Relay Mode', - `relay_node` text COLLATE utf8mb4_general_ci COMMENT 'Relay Node', - `speed_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Speed Limit', - `traffic_ratio` decimal(4,2) NOT NULL DEFAULT '0.00' COMMENT 'Traffic Ratio', - `group_id` bigint DEFAULT NULL COMMENT 'Group ID', - `protocol` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Protocol', - `config` text COLLATE utf8mb4_general_ci COMMENT 'Config', - `enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Enabled', - `sort` bigint NOT NULL DEFAULT '0' COMMENT 'Sort', - `last_reported_at` datetime(3) DEFAULT NULL COMMENT 'Last Reported Time', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`), - KEY `idx_group_id` (`group_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; --- ---------------------------- --- Table structure for server_group --- ---------------------------- -CREATE TABLE IF NOT EXISTS `server_group` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Group Name', - `description` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Group Description', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; --- ---------------------------- --- Table structure for sms --- ---------------------------- -CREATE TABLE IF NOT EXISTS `sms` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `content` text COLLATE utf8mb4_general_ci, - `platform` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL, - `area_code` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL, - `telephone` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL, - `status` tinyint(1) DEFAULT '1', - `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; --- ---------------------------- --- Table structure for subscribe --- ---------------------------- -CREATE TABLE IF NOT EXISTS `subscribe` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subscribe Name', - `description` text COLLATE utf8mb4_general_ci COMMENT 'Subscribe Description', - `unit_price` bigint NOT NULL DEFAULT '0' COMMENT 'Unit Price', - `unit_time` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Unit Time', - `discount` text COLLATE utf8mb4_general_ci COMMENT 'Discount', - `replacement` bigint NOT NULL DEFAULT '0' COMMENT 'Replacement', - `inventory` bigint NOT NULL DEFAULT '0' COMMENT 'Inventory', - `traffic` bigint NOT NULL DEFAULT '0' COMMENT 'Traffic', - `speed_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Speed Limit', - `device_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Device Limit', - `quota` bigint NOT NULL DEFAULT '0' COMMENT 'Quota', - `group_id` bigint DEFAULT NULL COMMENT 'Group Id', - `server_group` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Server Group', - `server` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Server', - `show` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Show portal page', - `sell` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Sell', - `sort` bigint NOT NULL DEFAULT '0' COMMENT 'Sort', - `deduction_ratio` bigint DEFAULT '0' COMMENT 'Deduction Ratio', - `allow_deduction` tinyint(1) DEFAULT '1' COMMENT 'Allow deduction', - `reset_cycle` bigint DEFAULT '0' COMMENT 'Reset Cycle: 0: No Reset, 1: 1st, 2: Monthly, 3: Yearly', - `renewal_reset` tinyint(1) DEFAULT '0' COMMENT 'Renew Reset', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; --- ---------------------------- --- Table structure for subscribe_group --- ---------------------------- -CREATE TABLE IF NOT EXISTS `subscribe_group` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Group Name', - `description` text COLLATE utf8mb4_general_ci COMMENT 'Group Description', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; --- ---------------------------- --- Table structure for subscribe_type --- ---------------------------- -CREATE TABLE IF NOT EXISTS `subscribe_type` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `name` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订阅类型', - `mark` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订阅标识', - `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间', - `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- ---------------------------- --- Table structure for system --- ---------------------------- -CREATE TABLE IF NOT EXISTS `system` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `category` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Category', - `key` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Key Name', - `value` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Key Value', - `type` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Type', - `desc` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Description', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`), - UNIQUE KEY `uni_system_key` (`key`), - KEY `index_key` (`key`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - - --- ---------------------------- --- Table structure for ticket --- ---------------------------- -CREATE TABLE IF NOT EXISTS `ticket` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `title` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Title', - `description` text COLLATE utf8mb4_general_ci COMMENT 'Description', - `user_id` bigint NOT NULL DEFAULT '0' COMMENT 'UserId', - `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Status', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- ---------------------------- --- Table structure for ticket_follow --- ---------------------------- -CREATE TABLE IF NOT EXISTS `ticket_follow` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `ticket_id` bigint NOT NULL DEFAULT '0' COMMENT 'TicketId', - `from` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'From', - `type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Type: 1 text, 2 image', - `content` text COLLATE utf8mb4_general_ci COMMENT 'Content', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- ---------------------------- --- Table structure for traffic_log --- ---------------------------- -CREATE TABLE IF NOT EXISTS `traffic_log` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `server_id` bigint NOT NULL COMMENT 'Server ID', - `user_id` bigint NOT NULL COMMENT 'User ID', - `subscribe_id` bigint NOT NULL COMMENT 'Subscription ID', - `download` bigint DEFAULT '0' COMMENT 'Download Traffic', - `upload` bigint DEFAULT '0' COMMENT 'Upload Traffic', - `timestamp` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Traffic Log Time', - PRIMARY KEY (`id`), - KEY `idx_subscribe_id` (`subscribe_id`), - KEY `idx_server_id` (`server_id`), - KEY `idx_user_id` (`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - - --- ---------------------------- --- Table structure for user --- ---------------------------- -CREATE TABLE IF NOT EXISTS `user` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `password` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'User Password', - `avatar` text COLLATE utf8mb4_general_ci COMMENT 'User Avatar', - `balance` bigint DEFAULT '0' COMMENT 'User Balance', - `telegram` bigint DEFAULT NULL COMMENT 'Telegram Account', - `refer_code` varchar(20) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Referral Code', - `referer_id` bigint DEFAULT NULL COMMENT 'Referrer ID', - `commission` bigint DEFAULT '0' COMMENT 'Commission', - `gift_amount` bigint DEFAULT '0' COMMENT 'User Gift Amount', - `enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Is Account Enabled', - `is_admin` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Admin', - `valid_email` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Email Verified', - `enable_email_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Email Notifications', - `enable_telegram_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Telegram Notifications', - `enable_balance_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Balance Change Notifications', - `enable_login_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Login Notifications', - `enable_subscribe_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Subscription Notifications', - `enable_trade_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Trade Notifications', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - `deleted_at` datetime(3) DEFAULT NULL COMMENT 'Deletion Time', - `is_del` bigint unsigned DEFAULT NULL COMMENT '1: Normal 0: Deleted', - PRIMARY KEY (`id`), - KEY `idx_referer` (`referer_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- ---------------------------- --- Table structure for user_auth_methods --- ---------------------------- -CREATE TABLE IF NOT EXISTS `user_auth_methods` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `user_id` bigint NOT NULL COMMENT 'User ID', - `auth_type` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Auth Type 1: apple 2: google 3: github 4: facebook 5: telegram 6: email 7: phone', - `auth_identifier` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Auth Identifier', - `verified` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Verified', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`), - KEY `idx_user_id` (`user_id`), - UNIQUE KEY `idx_auth_identifier` (`auth_identifier`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- ---------------------------- --- Table structure for user_balance_log --- ---------------------------- -CREATE TABLE IF NOT EXISTS `user_balance_log` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `user_id` bigint NOT NULL COMMENT 'User ID', - `amount` bigint NOT NULL COMMENT 'Amount', - `type` tinyint(1) NOT NULL COMMENT 'Type: 1: Recharge 2: Withdraw 3: Payment 4: Refund 5: Reward', - `order_id` bigint DEFAULT NULL COMMENT 'Order ID', - `balance` bigint NOT NULL COMMENT 'Balance', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', - PRIMARY KEY (`id`), - KEY `idx_user_id` (`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- ---------------------------- --- Table structure for user_commission_log --- ---------------------------- -CREATE TABLE IF NOT EXISTS `user_commission_log` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `user_id` bigint NOT NULL COMMENT 'User ID', - `order_no` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Order No.', - `amount` bigint NOT NULL COMMENT 'Amount', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', - PRIMARY KEY (`id`), - KEY `idx_user_id` (`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- ---------------------------- --- Table structure for user_device --- ---------------------------- -CREATE TABLE IF NOT EXISTS `user_device` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `user_id` bigint NOT NULL COMMENT 'User ID', - `subscribe_id` bigint DEFAULT NULL COMMENT 'Subscribe ID', - `ip` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Ip.', - `Identifier` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Identifier.', - `user_agent` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device User Agent.', - `online` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Online', - `enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'EnableDeviceNumber', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`), - KEY `idx_user_id` (`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- ---------------------------- --- Table structure for user_gift_amount_log --- ---------------------------- -CREATE TABLE IF NOT EXISTS `user_gift_amount_log` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `user_id` bigint NOT NULL COMMENT 'User ID', - `user_subscribe_id` bigint DEFAULT NULL COMMENT 'Deduction User Subscribe ID', - `order_no` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Order No.', - `type` tinyint(1) NOT NULL COMMENT 'Type: 1: Increase 2: Reduce', - `amount` bigint NOT NULL COMMENT 'Amount', - `balance` bigint NOT NULL COMMENT 'Balance', - `remark` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Remark', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', - PRIMARY KEY (`id`), - KEY `idx_user_id` (`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- ---------------------------- --- Table structure for user_subscribe --- ---------------------------- -CREATE TABLE IF NOT EXISTS `user_subscribe` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `user_id` bigint NOT NULL COMMENT 'User ID', - `order_id` bigint NOT NULL COMMENT 'Order ID', - `subscribe_id` bigint NOT NULL COMMENT 'Subscription ID', - `start_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Subscription Start Time', - `expire_time` datetime(3) DEFAULT NULL COMMENT 'Subscription Expire Time', - `traffic` bigint DEFAULT '0' COMMENT 'Traffic', - `download` bigint DEFAULT '0' COMMENT 'Download Traffic', - `upload` bigint DEFAULT '0' COMMENT 'Upload Traffic', - `token` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Token', - `uuid` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'UUID', - `status` tinyint(1) DEFAULT '0' COMMENT 'Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired 4: Deducted', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - `finished_at` datetime(3) DEFAULT NULL COMMENT 'Finished At', - PRIMARY KEY (`id`), - UNIQUE KEY `uni_user_subscribe_token` (`token`), - UNIQUE KEY `uni_user_subscribe_uuid` (`uuid`), - KEY `idx_user_id` (`user_id`), - KEY `idx_order_id` (`order_id`), - KEY `idx_subscribe_id` (`subscribe_id`), - KEY `idx_token` (`token`), - KEY `idx_uuid` (`uuid`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `server_rule_group` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Rule Group Name', - `icon` text COLLATE utf8mb4_general_ci COMMENT 'Rule Group Icon', - `description` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Rule Group Description', - `enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Rule Group Enable', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`), - UNIQUE KEY `unique_name` (`name`) -- Add unique constraint to `name` -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - - --- ---------------------------- --- Table structure for user_login_log --- ---------------------------- -CREATE TABLE IF NOT EXISTS `user_login_log` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `user_id` bigint NOT NULL COMMENT 'User ID', - `login_ip` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Login IP', - `user_agent` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'UserAgent', - `success` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Login Success', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', - PRIMARY KEY (`id`), - KEY `idx_user_id` (`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- ---------------------------- --- Table structure for user_subscribe_log --- ---------------------------- - -CREATE TABLE IF NOT EXISTS `user_subscribe_log` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `user_id` bigint NOT NULL COMMENT 'User ID', - `user_subscribe_id` bigint NOT NULL COMMENT 'User Subscribe ID', - `token` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Token', - `ip` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'IP', - `user_agent` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'UserAgent', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', - PRIMARY KEY (`id`), - KEY `idx_user_id` (`user_id`), - KEY `idx_user_subscribe_id` (`user_subscribe_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - - -CREATE TABLE IF NOT EXISTS `message_log` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `type` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'email' COMMENT 'Message Type', - `platform` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'smtp' COMMENT 'Platform', - `to` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'To', - `subject` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subject', - `content` text COLLATE utf8mb4_general_ci COMMENT 'Content', - `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Status', - `created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time', - `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -SET NAMES utf8mb4; -SET FOREIGN_KEY_CHECKS = 0; - --- ---------------------------- --- Table structure for user_device_online_record --- ---------------------------- -CREATE TABLE IF NOT EXISTS `user_device_online_record` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `user_id` bigint NULL DEFAULT NULL COMMENT 'User ID', - `identifier` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT 'Device Identifier', - `online_time` datetime(3) NULL DEFAULT NULL COMMENT 'Online Time', - `offline_time` datetime(3) NULL DEFAULT NULL COMMENT 'Offline Time', - `online_seconds` bigint NOT NULL DEFAULT '0' COMMENT 'Online Seconds ', - `duration_days` bigint NOT NULL DEFAULT '0' COMMENT 'Duration Days ', - `created_at` datetime(3) NULL DEFAULT NULL COMMENT 'Creation Time', - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; - -SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/initialize/migrate/init.go b/initialize/migrate/init.go index 3bfeba3..d5b4344 100644 --- a/initialize/migrate/init.go +++ b/initialize/migrate/init.go @@ -1,517 +1,15 @@ package migrate import ( - "fmt" "time" - "github.com/perfect-panel/ppanel-server/internal/model/auth" - "github.com/perfect-panel/ppanel-server/internal/model/payment" - "github.com/perfect-panel/ppanel-server/internal/model/subscribeType" - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/email" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/sms" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/uuidx" "gorm.io/gorm" ) -func InitPPanelSQL(db *gorm.DB) error { - logger.Info("PPanel SQL initialization started") - startTime := time.Now() - defer func() { - logger.Info("PPanel SQL initialization completed", logger.Field("duration", time.Since(startTime).String())) - - }() - return db.Transaction(func(tx *gorm.DB) error { - var err error - defer func() { - // If an error occurs, delete all tables - if err != nil { - logger.Debugf("PPanel SQL initialization completed, err: %v", err.Error()) - tables, _ := tx.Migrator().GetTables() - for _, table := range tables { - tx.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)) - } - } - }() - // init ppanel.sql file - if err = ExecuteSQLFile(tx, "database/ppanel.sql"); err != nil { - return err - } - //Insert basic system data - if err = insertBasicSystemData(tx); err != nil { - return err - } - // insert into OAuth config - if err = insertAuthMethodConfig(tx); err != nil { - return err - } - // insert into Payment config - if err = insertPaymentConfig(tx); err != nil { - return err - } - // insert into SubscribeType - if err = insertSubscribeType(tx); err != nil { - return err - } - return err - }) -} - -func insertBasicSystemData(tx *gorm.DB) error { - if err := insertSiteConfig(tx); err != nil { - return err - } - if err := insertSubscribeConfig(tx); err != nil { - return err - } - if err := insertVerifyConfig(tx); err != nil { - return err - } - if err := insertSeverConfig(tx); err != nil { - return err - } - if err := insertInviteConfig(tx); err != nil { - return err - } - if err := insertRegisterConfig(tx); err != nil { - return err - } - if err := insertCurrencyConfig(tx); err != nil { - return err - } - if err := insertVerifyCodeConfig(tx); err != nil { - return err - } - - version := system.System{ - Category: "system", - Key: "Version", - Value: constant.Version, - Type: "string", - Desc: "System Version", - } - if err := tx.Model(&system.System{}).Save(&version).Error; err != nil { - return err - } - - return nil -} - -// insertSiteConfig -func insertSiteConfig(tx *gorm.DB) error { - siteConfig := []system.System{ - { - Category: "site", - Key: "SiteLogo", - Value: "/favicon.svg", - Type: "string", - Desc: "Site Logo", - }, - { - Category: "site", - Key: "SiteName", - Value: "Perfect Panel", - Type: "string", - Desc: "Site Name", - }, - { - Category: "site", - Key: "SiteDesc", - Value: "PPanel is a pure, professional, and perfect open-source proxy panel tool, designed to be your ideal choice for learning and practical use.", - Type: "string", - Desc: "Site Description", - }, - { - Category: "site", - Key: "Host", - Value: "", - Type: "string", - Desc: "Site Host", - }, - { - Category: "site", - Key: "Keywords", - Value: "Perfect Panel,PPanel", - Type: "string", - Desc: "Site Keywords", - }, - { - Category: "site", - Key: "CustomHTML", - Value: "", - Type: "string", - Desc: "Custom HTML", - }, - { - Category: "site", - Key: "CustomData", - Value: "{\"website\":\"\",\"contacts\":{\"email\":\"\",\"telephone\":\"\",\"address\":\"\"},\"community\":{\"telegram\":\"\",\"twitter\":\"\",\"discord\":\"\",\"instagram\":\"\",\"linkedin\":\"\",\"facebook\":\"\",\"github\":\"\"}}", - Type: "string", - Desc: "Custom data", - }, - { - Category: "tos", - Key: "TosContent", - Value: "Welcome to use Perfect Panel", - Type: "string", - Desc: "Terms of Service", - }, - { - Category: "tos", - Key: "PrivacyPolicy", - Value: "", - Type: "string", - Desc: "PrivacyPolicy", - }, - { - Category: "ad", - Key: "WebAD", - Value: "false", - Type: "bool", - Desc: "Display ad on the web", - }, - } - return tx.Model(&system.System{}).Save(&siteConfig).Error -} - -// insertSubscribeConfig -func insertSubscribeConfig(tx *gorm.DB) error { - subscribeConfig := []system.System{ - { - Category: "subscribe", - Key: "SingleModel", - Value: "false", - Type: "bool", - Desc: "是否单订阅模式", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - }, - { - Category: "subscribe", - Key: "SubscribePath", - Value: "/api/subscribe", - Type: "string", - Desc: "订阅路径", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - }, - { - Category: "subscribe", - Key: "SubscribeDomain", - Value: "", - Type: "string", - Desc: "订阅域名", - }, - { - Category: "subscribe", - Key: "PanDomain", - Value: "false", - Type: "bool", - Desc: "是否使用泛域名", - }, - } - return tx.Model(&system.System{}).Save(&subscribeConfig).Error -} - -// insertVerifyConfig -func insertVerifyConfig(tx *gorm.DB) error { - verifyConfig := []system.System{ - { - Category: "verify", - Key: "TurnstileSiteKey", - Value: "", - Type: "string", - Desc: "TurnstileSiteKey", - }, - { - Category: "verify", - Key: "TurnstileSecret", - Value: "", - Type: "string", - Desc: "TurnstileSecret", - }, - { - Category: "verify", - Key: "EnableLoginVerify", - Value: "false", - Type: "bool", - Desc: "is enable login verify", - }, - { - Category: "verify", - Key: "EnableRegisterVerify", - Value: "false", - Type: "bool", - Desc: "is enable register verify", - }, - { - Category: "verify", - Key: "EnableResetPasswordVerify", - Value: "false", - Type: "bool", - Desc: "is enable reset password verify", - }, - } - return tx.Model(&system.System{}).Save(&verifyConfig).Error -} - -// insertSeverConfig -func insertSeverConfig(tx *gorm.DB) error { - serverConfig := []system.System{ - { - Category: "server", - Key: "NodeSecret", - Value: "12345678", - Type: "string", - Desc: "node secret", - }, - { - Category: "server", - Key: "NodePullInterval", - Value: "10", - Type: "int", - Desc: "node pull interval", - }, - { - Category: "server", - Key: "NodePushInterval", - Value: "60", - Type: "int", - Desc: "node push interval", - }, - { - Category: "server", - Key: "NodeMultiplierConfig", - Value: "[]", - Type: "string", - Desc: "node multiplier config", - }, - } - return tx.Model(&system.System{}).Save(&serverConfig).Error -} - -// insertInviteConfig -func insertInviteConfig(tx *gorm.DB) error { - inviteConfig := []system.System{ - { - Category: "invite", - Key: "ForcedInvite", - Value: "false", - Type: "bool", - Desc: "Forced invite", - }, - { - Category: "invite", - Key: "ReferralPercentage", - Value: "20", - Type: "int", - Desc: "Referral percentage", - }, - { - Category: "invite", - Key: "OnlyFirstPurchase", - Value: "false", - Type: "bool", - Desc: "Only first purchase", - }, - } - return tx.Model(&system.System{}).Save(&inviteConfig).Error -} - -// insertRegisterConfig -func insertRegisterConfig(tx *gorm.DB) error { - registerConfig := []system.System{ - { - Category: "register", - Key: "StopRegister", - Value: "false", - Type: "bool", - Desc: "is stop register", - }, - { - Category: "register", - Key: "EnableTrial", - Value: "false", - Type: "bool", - Desc: "is enable trial", - }, - { - Category: "register", - Key: "TrialSubscribe", - Value: "", - Type: "int", - Desc: "Trial subscription", - }, - { - Category: "register", - Key: "TrialTime", - Value: "24", - Type: "int", - Desc: "Trial time", - }, - { - Category: "register", - Key: "TrialTimeUnit", - Value: "Hour", - Type: "string", - Desc: "Trial time unit", - }, - { - Category: "register", - Key: "EnableIpRegisterLimit", - Value: "false", - Type: "bool", - Desc: "is enable IP register limit", - }, - { - Category: "register", - Key: "IpRegisterLimit", - Value: "3", - Type: "int", - Desc: "IP Register Limit", - }, - { - Category: "register", - Key: "IpRegisterLimitDuration", - Value: "64", - Type: "int", - Desc: "IP Register Limit Duration (minutes)", - }, - } - return tx.Model(&system.System{}).Save(®isterConfig).Error -} - -// insertAuthMethodConfig -func insertAuthMethodConfig(tx *gorm.DB) error { - // insert into OAuth config - var methods []auth.Auth - methods = append(methods, []auth.Auth{ - initEmailConfig(), - initMobileConfig(), - { - Method: "apple", - Config: new(auth.AppleAuthConfig).Marshal(), - }, - { - Method: "google", - Config: new(auth.GoogleAuthConfig).Marshal(), - }, - { - Method: "github", - Config: new(auth.GithubAuthConfig).Marshal(), - }, - { - Method: "facebook", - Config: new(auth.FacebookAuthConfig).Marshal(), - }, - { - - Method: "telegram", - Config: new(auth.TelegramAuthConfig).Marshal(), - }, - { - Method: "device", - Config: new(auth.DeviceConfig).Marshal(), - }, - }...) - return tx.Model(&auth.Auth{}).Save(&methods).Error -} - -// insertPaymentConfig -func insertPaymentConfig(tx *gorm.DB) error { - enable := true - payments := []payment.Payment{ - { - Id: -1, - Name: "Balance", - Platform: "balance", - Icon: "", - Domain: "", - Config: "", - FeeMode: 0, - FeePercent: 0, - FeeAmount: 0, - Enable: &enable, - }, - } - // reset auto increment - if err := tx.Exec("ALTER TABLE `payment` AUTO_INCREMENT = 1").Error; err != nil { - logger.Errorw("Reset auto increment failed", logger.Field("error", err)) - return err - } - return tx.Model(&payment.Payment{}).Save(&payments).Error -} - -// insertSubscribeType -func insertSubscribeType(tx *gorm.DB) error { - // insert into subscribe type - var subscribeTypes []subscribeType.SubscribeType - subscribeTypes = append(subscribeTypes, []subscribeType.SubscribeType{ - { - Name: "Clash", - Mark: "Clash", - }, - { - Name: "Hiddify", - Mark: "Hiddify", - }, - { - Name: "Loon", - Mark: "Loon", - }, - { - Name: "NekoBox", - Mark: "NekoBox", - }, - { - Name: "NekoRay", - Mark: "NekoRay", - }, - { - Name: "Netch", - Mark: "Netch", - }, - { - Name: "Quantumult", - Mark: "Quantumult", - }, - { - Name: "Shadowrocket", - Mark: "Shadowrocket", - }, - { - Name: "Singbox", - Mark: "Singbox", - }, - { - Name: "Surfboard", - Mark: "Surfboard", - }, - { - Name: "Surge", - Mark: "Surge", - }, - { - Name: "V2box", - Mark: "V2box", - }, - { - Name: "V2rayN", - Mark: "V2rayN", - }, - { - Name: "V2rayNg", - Mark: "V2rayNg", - }, - }...) - // insert into payment - return tx.Save(&subscribeTypes).Error -} - // CreateAdminUser create admin user func CreateAdminUser(email, password string, tx *gorm.DB) error { enable := true @@ -542,103 +40,3 @@ func CreateAdminUser(email, password string, tx *gorm.DB) error { return nil }) } - -func initEmailConfig() auth.Auth { - enable := true - smtpConfig := new(auth.SMTPConfig) - emailConfig := auth.EmailAuthConfig{ - Platform: "smtp", - PlatformConfig: smtpConfig, - EnableVerify: false, - EnableDomainSuffix: false, - DomainSuffixList: "", - VerifyEmailTemplate: email.DefaultEmailVerifyTemplate, - ExpirationEmailTemplate: email.DefaultExpirationEmailTemplate, - MaintenanceEmailTemplate: email.DefaultMaintenanceEmailTemplate, - TrafficExceedEmailTemplate: email.DefaultTrafficExceedEmailTemplate, - } - authMethod := auth.Auth{ - Method: "email", - Config: emailConfig.Marshal(), - Enabled: &enable, - } - return authMethod -} - -func initMobileConfig() auth.Auth { - cfg := new(auth.AlibabaCloudConfig) - mobileConfig := auth.MobileAuthConfig{ - Platform: sms.AlibabaCloud.String(), - PlatformConfig: cfg, - EnableWhitelist: false, - Whitelist: make([]string, 0), - } - authMethod := auth.Auth{ - Method: "mobile", - Config: mobileConfig.Marshal(), - } - return authMethod -} - -// insert into currency config -func insertCurrencyConfig(tx *gorm.DB) error { - currencyConfig := []system.System{ - { - Category: "currency", - Key: "Currency", - Value: "USD", - Type: "string", - Desc: "Currency", - }, - { - Category: "currency", - Key: "CurrencySymbol", - Value: "$", - Type: "string", - Desc: "Currency Symbol", - }, - { - Category: "currency", - Key: "CurrencyUnit", - Value: "USD", - Type: "string", - Desc: "Currency Unit", - }, - { - Category: "currency", - Key: "AccessKey", - Value: "", - Type: "string", - Desc: "Exchangerate Access Key", - }, - } - return tx.Model(&system.System{}).Save(¤cyConfig).Error -} - -// insert into verify code config -func insertVerifyCodeConfig(tx *gorm.DB) error { - verifyCodeConfig := []system.System{ - { - Category: "verify_code", - Key: "VerifyCodeExpireTime", - Value: "300", - Type: "int", - Desc: "Verify code expire time", - }, - { - Category: "verify_code", - Key: "VerifyCodeLimit", - Value: "15", - Type: "int", - Desc: "limits of verify code", - }, - { - Category: "verify_code", - Key: "VerifyCodeInterval", - Value: "60", - Type: "int", - Desc: "Interval of verify code", - }, - } - return tx.Model(&system.System{}).Save(&verifyCodeConfig).Error -} diff --git a/initialize/migrate/init_test.go b/initialize/migrate/init_test.go index 183c3f5..278a35f 100644 --- a/initialize/migrate/init_test.go +++ b/initialize/migrate/init_test.go @@ -1,37 +1 @@ package migrate - -import ( - "testing" - - "github.com/perfect-panel/ppanel-server/pkg/orm" - "gorm.io/gorm" -) - -func connMySQL() *gorm.DB { - - cfg := orm.Config{ - Addr: "127.0.0.1", - Username: "root", - Password: "mylove520", - Dbname: "ppanel", - } - db, err := orm.ConnectMysql(orm.Mysql{ - Config: cfg, - }) - if err != nil { - return nil - } - return db -} -func TestInitPPanelSQL(t *testing.T) { - t.Skipf("Skip TestInitPPanelSQL") - db := connMySQL() - if db == nil { - t.Error("connect mysql failed") - return - } - if err := InitPPanelSQL(db); err != nil { - t.Error(err) - } - t.Logf("InitPPanelSQL success") -} diff --git a/initialize/migrate/migrate.go b/initialize/migrate/migrate.go index 1e227fd..a2985f5 100644 --- a/initialize/migrate/migrate.go +++ b/initialize/migrate/migrate.go @@ -2,33 +2,28 @@ package migrate import ( "embed" - "time" + "fmt" - "github.com/perfect-panel/ppanel-server/pkg/logger" - - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/internal/svc" + "github.com/golang-migrate/migrate/v4" + _ "github.com/golang-migrate/migrate/v4/database/mysql" + "github.com/golang-migrate/migrate/v4/source/iofs" + "github.com/perfect-panel/server/pkg/logger" ) //go:embed database/*.sql var sqlFiles embed.FS +var NoChange = migrate.ErrNoChange -func Migrate(ctx *svc.ServiceContext) { - logger.Debug("SQL Migrate started") - startTime := time.Now() - defer func() { - logger.WithDuration(time.Since(startTime)).Debug("PPanel SQL Migrate completed") - }() - db := ctx.DB - if !db.Migrator().HasTable(&system.System{}) { - if err := InitPPanelSQL(db); err != nil { - logger.Error("SQL Migrate failed", logger.Field("err", err.Error())) - panic(err) - } - // create admin user - if err := CreateAdminUser(ctx.Config.Administrator.Email, ctx.Config.Administrator.Password, db); err != nil { - logger.Error("Create admin User failed", logger.Field("err", err.Error())) - panic(err) - } +func Migrate(dsn string) *migrate.Migrate { + d, err := iofs.New(sqlFiles, "database") + if err != nil { + logger.Errorf("[Migrate] iofs.New error: %v", err.Error()) + panic(err) } + client, err := migrate.NewWithSourceInstance("iofs", d, fmt.Sprintf("mysql://%s", dsn)) + if err != nil { + logger.Errorf("[Migrate] NewWithSourceInstance error: %v", err.Error()) + panic(err) + } + return client } diff --git a/initialize/migrate/migrate_test.go b/initialize/migrate/migrate_test.go new file mode 100644 index 0000000..531266e --- /dev/null +++ b/initialize/migrate/migrate_test.go @@ -0,0 +1,49 @@ +package migrate + +import ( + "testing" + + "github.com/perfect-panel/server/internal/model/node" + "github.com/perfect-panel/server/pkg/orm" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +func getDSN() string { + + cfg := orm.Config{ + Addr: "127.0.0.1", + Username: "root", + Password: "mylove520", + Dbname: "vpnboard", + } + mc := orm.Mysql{ + Config: cfg, + } + return mc.Dsn() +} + +func TestMigrate(t *testing.T) { + t.Skipf("skip test") + m := Migrate(getDSN()) + err := m.Migrate(2004) + if err != nil { + t.Errorf("failed to migrate: %v", err) + } else { + t.Log("migrate success") + } +} +func TestMysql(t *testing.T) { + db, err := gorm.Open(mysql.New(mysql.Config{ + DSN: "root:mylove520@tcp(localhost:3306)/vpnboard", + })) + if err != nil { + t.Fatalf("Failed to connect to MySQL: %v", err) + } + err = db.Migrator().AutoMigrate(&node.Node{}) + if err != nil { + t.Fatalf("Failed to auto migrate: %v", err) + return + } + t.Log("MySQL connection and migration successful") +} diff --git a/initialize/migrate/patch/01703.go b/initialize/migrate/patch/01703.go deleted file mode 100644 index b49392f..0000000 --- a/initialize/migrate/patch/01703.go +++ /dev/null @@ -1,456 +0,0 @@ -package patch - -import ( - "github.com/perfect-panel/ppanel-server/initialize/migrate" - "github.com/perfect-panel/ppanel-server/internal/model/application" - "github.com/perfect-panel/ppanel-server/internal/model/auth" - "github.com/perfect-panel/ppanel-server/internal/model/log" - "github.com/perfect-panel/ppanel-server/internal/model/server" - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/email" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/sms" - "gorm.io/gorm" -) - -func Migrate01200(db *gorm.DB) error { - var version = "0.1.2(01200)" - return db.Transaction(func(tx *gorm.DB) error { - if exists := db.Migrator().HasColumn(&user.OldUser{}, "email"); !exists { - logger.Debug("Migrate 01200 skipped", logger.Field("reason", "old user table not exists")) - return nil - } - - logger.Debug("Migrate 01200 started", logger.Field("step", "1"), logger.Field("action", "migrate old user to user auth methods")) - var users []*user.OldUser - if err := tx.Model(&user.OldUser{}).Find(&users).Error; err != nil { - return err - } - if err := tx.Migrator().AutoMigrate(&user.AuthMethods{}); err != nil { - logger.Errorw("Migrate 01200 failed", logger.Field("step", "1"), logger.Field("action", "create user auth methods table"), logger.Field("error", err.Error())) - return err - } - err := tx.Transaction(func(tx *gorm.DB) error { - for _, oldUser := range users { - if oldUser.Email == "" { - continue - } - // create user auth method - authMethod := &user.AuthMethods{ - UserId: oldUser.Id, - AuthType: "email", - AuthIdentifier: oldUser.Email, - Verified: false, - } - if err := tx.Create(authMethod).Error; err != nil { - return err - } - } - return nil - }) - if err != nil { - logger.Errorw("Migrate 01200 failed", logger.Field("step", "1"), logger.Field("action", "migrate old user to user auth methods"), logger.Field("error", err.Error())) - return err - } - logger.Debug("Migrate 01200 completed", logger.Field("step", "1"), logger.Field("action", "migrate old user to user auth methods")) - - logger.Debug("Migrate 01200 started", logger.Field("step", "2"), logger.Field("action", "exclude sql files")) - // exclude sql files - if err := migrate.ExecuteSQLFile(tx, "database/01200-patch.sql"); err != nil { - logger.Errorw("Migrate 01200 failed", logger.Field("step", "2"), logger.Field("action", "exclude sql files"), logger.Field("file", "database/01200-patch.sql"), logger.Field("error", err.Error())) - return err - } - logger.Debug("Migrate 01200 completed", logger.Field("step", "2"), logger.Field("action", "exclude sql files")) - - logger.Debug("Migrate 01200 started", logger.Field("step", "3"), logger.Field("action", "update system config")) - versionConfig := &system.System{ - Category: "system", - Key: "Version", - Value: version, - Type: "string", - Desc: "Version of the system, eg: 1.0.0(10000)", - } - // update system config - if err := tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Save(&versionConfig).Error; err != nil { - logger.Errorw("Migrate 01200 failed", logger.Field("step", "3"), logger.Field("action", "update system config"), logger.Field("error", err.Error())) - return err - } - return nil - }) -} - -func Migrate01201(db *gorm.DB) error { - version := "0.1.2(01201)" - // exclude sql files - if err := migrate.ExecuteSQLFile(db, "database/01201-patch.sql"); err != nil { - logger.Errorw("Migrate 01201 failed", logger.Field("step", "1"), logger.Field("action", "exclude sql files"), logger.Field("file", "database/01200-patch.sql"), logger.Field("error", err.Error())) - return err - } - // update system config - if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil { - logger.Errorw("Migrate 01201 failed", logger.Field("step", "2"), logger.Field("action", "update system config"), logger.Field("error", err.Error())) - return err - } - return nil -} - -func Migrate01202(db *gorm.DB) error { - version := "0.1.2(01202)" - return db.Transaction(func(tx *gorm.DB) error { - // migrate email config to system config - if err := db.Migrator().AutoMigrate(&auth.Auth{}); err != nil { - logger.Errorw("Migrate01202: AutoMigrate Auth failed", logger.Field("version", version), logger.Field("error", err.Error())) - return err - } - if db.Migrator().HasColumn("oauth_config", "platform") { - if err := db.Migrator().RenameColumn("oauth_config", "platform", "method"); err != nil { - logger.Errorw("Migrate01202: RenameColumn platform to method failed", logger.Field("version", version), logger.Field("error", err.Error())) - } - } - - // init email config - if err := initEmailConfig(db); err != nil { - logger.Errorw("Migrate01202: initEmailConfig failed", logger.Field("version", version), logger.Field("error", err.Error())) - return err - } - // init mobile config - if err := initMobileConfig(db); err != nil { - logger.Errorw("Migrate01202: initMobileConfig failed", logger.Field("version", version), logger.Field("error", err.Error())) - return err - } - // drop oauth_config table - err := db.Migrator().DropTable("oauth_config") - if err != nil { - logger.Debug("Migrate01202: DropTable oauth_config failed", logger.Field("version", version), logger.Field("error", err.Error())) - } - // exclude sql files - if err := migrate.ExecuteSQLFile(db, "database/01202-patch.sql"); err != nil { - logger.Errorw("Migrate 01202 failed", logger.Field("action", "exclude sql files"), logger.Field("file", "database/012002-patch.sql"), logger.Field("error", err.Error())) - return err - } - - // update system config - if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil { - logger.Errorw("Migrate 01202 failed", logger.Field("step", "2"), logger.Field("action", "update system config"), logger.Field("error", err.Error())) - return err - } - return nil - }) -} -func Migrate01203(db *gorm.DB) error { - version := "0.1.2(01203)" - return db.Transaction(func(tx *gorm.DB) error { - if err := db.AutoMigrate(&user.LoginLog{}, &user.SubscribeLog{}); err != nil { - logger.Errorw("Migrate01203: AutoMigrate LoginLog/SubscribeLog failed", logger.Field("version", version), logger.Field("error", err.Error())) - return err - } - // update version - if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil { - logger.Errorw("Migrate01203: Update Version failed", logger.Field("version", version), logger.Field("error", err.Error())) - return err - } - return nil - }) -} - -func Migrate01204(db *gorm.DB) error { - version := "0.1.2(01204)" - return db.Transaction(func(tx *gorm.DB) error { - if err := db.AutoMigrate(&log.MessageLog{}); err != nil { - logger.Errorw("Migrate01204: AutoMigrate MessageLog failed", logger.Field("version", version), logger.Field("error", err.Error())) - return err - } - // Trial configuration - if err := initTrialConfig(tx); err != nil { - logger.Errorw("Migrate01204: initTrialConfig failed", logger.Field("version", version), logger.Field("error", err.Error())) - return err - } - // Add auth method with device - if err := addAuthMethodWithDevice(tx); err != nil { - logger.Errorw("Migrate01204: Add auth method with device failed", logger.Field("version", version), logger.Field("error", err.Error())) - return err - } - // update version - if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil { - logger.Errorw("Migrate01204: Update Version failed", logger.Field("version", version), logger.Field("error", err.Error())) - return err - } - - return nil - }) -} - -func Migrate01205(db *gorm.DB) error { - version := "0.1.2(01205)" - return db.Transaction(func(tx *gorm.DB) error { - // Add VerifyCode public configuration - configs := []system.System{ - { - Category: "verify_code", - Key: "VerifyCodeExpireTime", - Value: "5", - Type: "int", - Desc: "Verify code expire time", - }, - { - Category: "verify_code", - Key: "VerifyCodeLimit", - Value: "15", - Type: "int", - Desc: "limits of verify code", - }, - { - Category: "verify_code", - Key: "VerifyCodeInterval", - Value: "60", - Type: "int", - Desc: "Interval of verify code", - }, - } - if err := tx.Model(&system.System{}).Save(&configs).Error; err != nil { - logger.Errorw("Migrate01205: Save VerifyCode public configuration failed", logger.Field("error", err.Error())) - return err - } - - // update version - if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil { - logger.Errorw("Migrate01205: Update Version failed", logger.Field("version", version), logger.Field("error", err.Error())) - return err - } - return nil - }) -} - -func Migrate01301(db *gorm.DB) error { - version := "0.1.3(01301)" - return db.Transaction(func(tx *gorm.DB) error { - err := tx.Migrator().AlterColumn(&application.Application{}, "icon") - if err != nil { - logger.Errorw("Migrate01301: AlterColumn failed", logger.Field("error", err.Error())) - return err - } - // update version - if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil { - logger.Errorw("Migrate01205: Update Version failed", logger.Field("version", version), logger.Field("error", err.Error())) - return err - } - return nil - }) -} - -func Migrate01602(db *gorm.DB) error { - version := "0.1.6(01602)" - return db.Transaction(func(tx *gorm.DB) error { - if tx.Model(&system.System{}).Where("`category` = 'tos' AND `key` = 'TosContent'").Find(&system.System{}).RowsAffected == 0 { - if err := tx.Save(&system.System{ - Category: "tos", - Key: "TosContent", - Value: "Welcome to use Perfect Panel", - Type: "string", - Desc: "Terms of Service", - }).Error; err != nil { - return err - } - } - // update version - if err := tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil { - return err - } - return nil - }) -} -func Migrate01701(db *gorm.DB) error { - return db.Transaction(func(tx *gorm.DB) error { - version := "0.1.7(01701)" - if err := db.Migrator().AlterColumn(&user.User{}, "Avatar"); err != nil { - return err - } - // update version - if err := tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil { - return err - } - return nil - }) - -} - -func Migrate01702(db *gorm.DB) error { - return db.Transaction(func(tx *gorm.DB) error { - version := "0.1.7(01702)" - - if tx.Model(&system.System{}).Where("`category` = 'site' AND `key` = 'Keywords'").Find(&system.System{}).RowsAffected == 0 { - if err := tx.Save(&system.System{ - Category: "site", - Key: "Keywords", - Value: "Perfect Panel,PPanel", - Type: "string", - Desc: "Keywords", - }).Error; err != nil { - return err - } - } - if tx.Model(&system.System{}).Where("`category` = 'site' AND `key` = 'CustomHTML'").Find(&system.System{}).RowsAffected == 0 { - if err := tx.Save(&system.System{ - Category: "site", - Key: "CustomHTML", - Value: "", - Type: "string", - Desc: "Custom HTML", - }).Error; err != nil { - return err - } - } - - // update version - if err := tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil { - return err - } - return nil - }) -} - -func Migrate01703(db *gorm.DB) error { - version := "0.1.7(01703)" - return db.Transaction(func(tx *gorm.DB) error { - if tx.Model(&system.System{}).Where("`category` = 'tos' AND `key` = 'PrivacyPolicy'").Find(&system.System{}).RowsAffected == 0 { - if err := tx.Save(&system.System{ - Category: "tos", - Key: "PrivacyPolicy", - Value: "", - Type: "string", - Desc: "Privacy Policy", - }).Error; err != nil { - return err - } - } - return tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error - }) -} -func Migrate01704(db *gorm.DB) error { - version := "0.1.7(01704)" - - // check server table latitude column exists, if not exists, create it - if exists := db.Migrator().HasColumn(&server.Server{}, "latitude"); !exists { - if err := db.Migrator().AddColumn(&server.Server{}, "latitude"); err != nil { - logger.Errorw("Migrate 01704 failed", logger.Field("action", "add latitude column"), logger.Field("error", err.Error())) - return err - } - logger.Infow("Migrate 01704 success", logger.Field("action", "add latitude column")) - } - // check server table longitude column exists, if not exists, create it - if exists := db.Migrator().HasColumn(&server.Server{}, "longitude"); !exists { - if err := db.Migrator().AddColumn(&server.Server{}, "longitude"); err != nil { - logger.Errorw("Migrate 01704 failed", logger.Field("action", "add longitude column"), logger.Field("error", err.Error())) - return err - } - logger.Infow("Migrate 01704 success", logger.Field("action", "add longitude column")) - } - // update system config - if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil { - logger.Errorw("Migrate 01704 failed", logger.Field("step", "2"), logger.Field("action", "update system config"), logger.Field("error", err.Error())) - return err - } - logger.Infow("Migrate 01704 success", logger.Field("action", "update system config")) - return nil -} - -func Migrate01705(db *gorm.DB) error { - version := "0.1.7(01705)" - // check user_device table exists, if not exists, create it - if exists := db.Migrator().HasTable(&user.Device{}); !exists { - if err := db.Migrator().CreateTable(&user.Device{}); err != nil { - logger.Errorw("Migrate 01705 failed", logger.Field("action", "create user_device table"), logger.Field("error", err.Error())) - return err - } - logger.Infow("Migrate 01705 success", logger.Field("action", "create user_device table")) - } - // check user_table exists and imei column exists, if exists, update imei column name to identifier - if exists := db.Migrator().HasColumn(&user.Device{}, "imei"); exists { - if err := db.Migrator().RenameColumn(&user.Device{}, "imei", "identifier"); err != nil { - logger.Errorw("Migrate 01705 failed", logger.Field("action", "rename imei column to identifier"), logger.Field("error", err.Error())) - return err - } - logger.Infow("Migrate 01705 success", logger.Field("action", "rename imei column to identifier")) - } - - // update system config - if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil { - logger.Errorw("Migrate 01705 failed", logger.Field("step", "2"), logger.Field("action", "update system config"), logger.Field("error", err.Error())) - return err - } - return nil -} - -func initMobileConfig(db *gorm.DB) error { - cfg := new(auth.AlibabaCloudConfig) - mobileConfig := auth.MobileAuthConfig{ - Platform: sms.AlibabaCloud.String(), - PlatformConfig: cfg.Marshal(), - EnableWhitelist: false, - Whitelist: make([]string, 0), - } - authMethod := auth.Auth{ - Method: "mobile", - Config: mobileConfig.Marshal(), - } - if err := db.Save(&authMethod).Error; err != nil { - return err - } - return nil -} - -func initEmailConfig(db *gorm.DB) error { - enable := true - smtpConfig := new(auth.SMTPConfig) - - emailConfig := auth.EmailAuthConfig{ - Platform: "smtp", - PlatformConfig: smtpConfig.Marshal(), - EnableVerify: false, - EnableDomainSuffix: false, - DomainSuffixList: "", - VerifyEmailTemplate: email.DefaultEmailVerifyTemplate, - ExpirationEmailTemplate: email.DefaultExpirationEmailTemplate, - MaintenanceEmailTemplate: email.DefaultMaintenanceEmailTemplate, - } - authMethod := auth.Auth{ - Method: "email", - Config: emailConfig.Marshal(), - Enabled: &enable, - } - return db.Save(&authMethod).Error -} - -func initTrialConfig(tx *gorm.DB) error { - configs := []system.System{ - { - Category: "register", - Key: "TrialSubscribe", - Value: "", - Type: "int", - Desc: "Trial subscription", - }, - { - Category: "register", - Key: "TrialTime", - Value: "24", - Type: "int", - Desc: "Trial time", - }, - { - Category: "register", - Key: "TrialTimeUnit", - Value: "Hour", - Type: "string", - Desc: "Trial time unit", - }, - } - return tx.Model(&system.System{}).Save(&configs).Error -} - -func addAuthMethodWithDevice(tx *gorm.DB) error { - return tx.Model(&auth.Auth{}).Save(&auth.Auth{ - Method: "device", - }).Error -} diff --git a/initialize/migrate/patch/02000.go b/initialize/migrate/patch/02000.go deleted file mode 100644 index cf67ebb..0000000 --- a/initialize/migrate/patch/02000.go +++ /dev/null @@ -1,252 +0,0 @@ -package patch - -import ( - "github.com/perfect-panel/ppanel-server/internal/model/ads" - "github.com/perfect-panel/ppanel-server/internal/model/application" - "github.com/perfect-panel/ppanel-server/internal/model/auth" - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/model/payment" - "github.com/perfect-panel/ppanel-server/internal/model/server" - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "gorm.io/gorm" -) - -func Migrate02000(db *gorm.DB) error { - version := "0.2.0(02000)" - return db.Transaction(func(tx *gorm.DB) error { - if err := initDeviceConfig(tx); err != nil { - logMigrationError("Setting Device Config", err) - return err - } - logMigrationSuccess("Setting Device Config") - - if !tx.Migrator().HasTable(&ads.Ads{}) { - if err := createAdsTable(tx); err != nil { - return err - } - } - - if err := updatePaymentTable(tx); err != nil { - return err - } - - if err := tx.Migrator().AutoMigrate(&order.Order{}); err != nil { - logMigrationError("Auto Migrate Order", err) - return err - } - - return updateSystemVersion(tx, version) - }) -} - -func Migrate02001(db *gorm.DB) error { - version := "0.2.0(02001)" - return db.Transaction(func(tx *gorm.DB) error { - if tx.Model(&system.System{}).Where("`category` = 'site' AND `key` = 'CustomData'").Find(&system.System{}).RowsAffected == 0 { - if err := tx.Save(&system.System{ - Category: "site", - Key: "CustomData", - Value: "{\"website\":\"\",\"contacts\":{\"email\":\"\",\"telephone\":\"\",\"address\":\"\"},\"community\":{\"telegram\":\"\",\"twitter\":\"\",\"discord\":\"\",\"instagram\":\"\",\"linkedin\":\"\",\"facebook\":\"\",\"github\":\"\"}}", - Type: "string", - Desc: "Custom data", - }).Error; err != nil { - logMigrationError("create custom data system config", err) - return err - } - } - return updateSystemVersion(tx, version) - }) -} - -func Migrate02002(db *gorm.DB) error { - version := "0.2.0(02002)" - return db.Transaction(func(tx *gorm.DB) error { - if err := tx.Model(&system.System{}).Where("`category` = 'site' AND `key` = 'CustomData'").UpdateColumn("type", "string").Error; err != nil { - return err - } - return updateSystemVersion(tx, version) - }) -} - -func Migrate02003(db *gorm.DB) error { - version := "0.2.0(02003)" - return db.Transaction(func(tx *gorm.DB) error { - if err := addColumnIfNotExists(tx, &order.Order{}, "payment_id"); err != nil { - return err - } - if err := addColumnIfNotExists(tx, &payment.Payment{}, "platform"); err != nil { - return err - } - if err := dropColumnIfExists(tx, &payment.Payment{}, "mark"); err != nil { - return err - } - if err := addColumnIfNotExists(tx, &payment.Payment{}, "description"); err != nil { - return err - } - if err := addColumnIfNotExists(tx, &payment.Payment{}, "token"); err != nil { - return err - } - return updateSystemVersion(tx, version) - }) -} - -func Migrate02007(db *gorm.DB) error { - version := "0.2.0(02007)" - return db.Transaction(func(tx *gorm.DB) error { - if err := recreateTable(tx, &server.RuleGroup{}); err != nil { - return err - } - return updateSystemVersion(tx, version) - }) -} - -func Migrate02008(db *gorm.DB) error { - version := "0.2.0(02008)" - return db.Transaction(func(tx *gorm.DB) error { - if exists := tx.Migrator().HasColumn(&application.ApplicationConfig{}, "invitation_link"); !exists { - if err := tx.Migrator().AddColumn(&application.ApplicationConfig{}, "invitation_link"); err != nil { - logger.Errorw("Migrate 02008 failed", logger.Field("action", "add invitation_link column"), logger.Field("error", err.Error())) - return err - } - logger.Infow("Migrate 02008 success", logger.Field("action", "add invitation_link column")) - } - - if exists := tx.Migrator().HasTable(&user.DeviceOnlineRecord{}); !exists { - if err := tx.Migrator().CreateTable(&user.DeviceOnlineRecord{}); err != nil { - logger.Errorw("Migrate 02008 failed", logger.Field("action", "create device_online_record table"), logger.Field("error", err.Error())) - return err - } - } - return tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error - }) -} - -func Migrate02009(db *gorm.DB) error { - version := "0.2.0(02009)" - return db.Transaction(func(tx *gorm.DB) error { - if err := addColumnIfNotExists(tx, &user.Subscribe{}, "finished_at"); err != nil { - logger.Errorw("Migrate 02009 failed", logger.Field("action", "subscribe table add finished_at column"), logger.Field("error", err.Error())) - return err - } - return updateSystemVersion(tx, version) - }) -} - -func Migrate02010(db *gorm.DB) error { - version := "0.2.0(02010)" - return db.Transaction(func(tx *gorm.DB) error { - if err := addColumnIfNotExists(tx, &application.ApplicationConfig{}, "kr_website_id"); err != nil { - logger.Errorw("Migrate 02010 failed", logger.Field("action", "application_config table add kr_website_id column"), logger.Field("error", err.Error())) - return err - } - return updateSystemVersion(tx, version) - }) -} - -func Migrate02011(db *gorm.DB) error { - version := "0.2.0(02011)" - return db.Transaction(func(tx *gorm.DB) error { - if err := addColumnIfNotExists(tx, &user.Subscribe{}, "used_period"); err != nil { - logger.Errorw("Migrate 02011 failed", logger.Field("action", "user.Subscribe table add used_period column"), logger.Field("error", err.Error())) - return err - } - if err := addColumnIfNotExists(tx, &user.Subscribe{}, "total_period"); err != nil { - logger.Errorw("Migrate 02011 failed", logger.Field("action", "user.Subscribe table add total_period column"), logger.Field("error", err.Error())) - return err - } - return updateSystemVersion(tx, version) - }) -} - -func initDeviceConfig(db *gorm.DB) error { - cfg := new(auth.DeviceConfig) - return db.Model(&auth.Auth{}).Where("method = ?", "device").Update("config", cfg.Marshal()).Error -} - -func createAdsTable(tx *gorm.DB) error { - if err := tx.Migrator().CreateTable(&ads.Ads{}); err != nil { - logMigrationError("Create Table Ads", err) - return err - } - logMigrationSuccess("Create Table Ads") - return tx.Model(&system.System{}).Save(&system.System{ - Category: "ad", - Key: "WebAD", - Value: "false", - Type: "bool", - Desc: "Display ad on the web", - }).Error -} - -func updatePaymentTable(tx *gorm.DB) error { - if err := tx.Exec("DROP TABLE IF EXISTS `payment`").Error; err != nil { - logMigrationError("Drop Payment Table", err) - } - if err := tx.AutoMigrate(&payment.Payment{}); err != nil { - logMigrationError("Auto Migrate Payment", err) - return err - } - enable := true - return tx.Model(&payment.Payment{}).Create(&payment.Payment{ - Id: -1, - Name: "", - Platform: "balance", - Icon: "", - Domain: "", - Config: "", - FeeMode: 0, - FeePercent: 0, - FeeAmount: 0, - Enable: &enable, - }).Error -} - -func updateSystemVersion(tx *gorm.DB, version string) error { - return tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error -} - -func addColumnIfNotExists(tx *gorm.DB, model interface{}, columnName string) error { - if exists := tx.Migrator().HasColumn(model, columnName); !exists { - if err := tx.Migrator().AddColumn(model, columnName); err != nil { - logMigrationError("add "+columnName+" column", err) - return err - } - logMigrationSuccess("add " + columnName + " column") - } - return nil -} - -func dropColumnIfExists(tx *gorm.DB, model interface{}, columnName string) error { - if exists := tx.Migrator().HasColumn(model, columnName); exists { - if err := tx.Migrator().DropColumn(model, columnName); err != nil { - logMigrationError("del "+columnName+" column", err) - return err - } - logMigrationSuccess("del " + columnName + " column") - } - return nil -} - -func recreateTable(tx *gorm.DB, model interface{}) error { - if exists := tx.Migrator().HasTable(model); exists { - if err := tx.Migrator().DropTable(model); err != nil { - logMigrationError("drop table", err) - return err - } - } - if err := tx.Migrator().CreateTable(model); err != nil { - logMigrationError("create table", err) - return err - } - return nil -} - -func logMigrationError(action string, err error) { - logger.Errorw("Migration failed", logger.Field("action", action), logger.Field("error", err.Error())) -} - -func logMigrationSuccess(action string) { - logger.Infow("Migration success", logger.Field("action", action)) -} diff --git a/initialize/migrate/patch/03001.go b/initialize/migrate/patch/03001.go deleted file mode 100644 index 144ba32..0000000 --- a/initialize/migrate/patch/03001.go +++ /dev/null @@ -1,30 +0,0 @@ -package patch - -import ( - "github.com/perfect-panel/ppanel-server/internal/model/application" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "gorm.io/gorm" -) - -func Migrate03001(db *gorm.DB) error { - version := "0.3.0(1)" - return db.Transaction(func(tx *gorm.DB) error { - if err := addColumnIfNotExists(tx, &user.Subscribe{}, "finished_at"); err != nil { - logger.Errorw("Migrate 03001 failed", logger.Field("action", "user.Subscribe table add finished_at column"), logger.Field("error", err.Error())) - return err - } - return updateSystemVersion(tx, version) - }) -} - -func Migrate03002(db *gorm.DB) error { - version := "0.3.0(2)" - return db.Transaction(func(tx *gorm.DB) error { - if err := addColumnIfNotExists(tx, &application.ApplicationConfig{}, "kr_website_id"); err != nil { - logger.Errorw("Migrate 03002 failed", logger.Field("action", "application.Config table add kr_website_id column"), logger.Field("error", err.Error())) - return err - } - return updateSystemVersion(tx, version) - }) -} diff --git a/initialize/mobile.go b/initialize/mobile.go index 696a725..ac784ea 100644 --- a/initialize/mobile.go +++ b/initialize/mobile.go @@ -3,14 +3,13 @@ package initialize import ( "context" "encoding/json" - "fmt" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "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 Mobile(ctx *svc.ServiceContext) { @@ -21,9 +20,7 @@ func Mobile(ctx *svc.ServiceContext) { } var cfg config.MobileConfig var mobileConfig auth.MobileAuthConfig - if err := mobileConfig.Unmarshal(method.Config); err != nil { - panic(fmt.Sprintf("failed to unmarshal mobile auth config: %v", err.Error())) - } + mobileConfig.Unmarshal(method.Config) tool.DeepCopy(&cfg, mobileConfig) cfg.Enable = *method.Enabled value, _ := json.Marshal(mobileConfig.PlatformConfig) diff --git a/initialize/mysql.go b/initialize/mysql.go index 17a45e2..723178f 100644 --- a/initialize/mysql.go +++ b/initialize/mysql.go @@ -1,10 +1 @@ package initialize - -import ( - "github.com/perfect-panel/ppanel-server/initialize/migrate" - "github.com/perfect-panel/ppanel-server/internal/svc" -) - -func Mysql(ctx *svc.ServiceContext) { - migrate.Migrate(ctx) -} diff --git a/initialize/node.go b/initialize/node.go index a8d65bb..51dce08 100644 --- a/initialize/node.go +++ b/initialize/node.go @@ -3,14 +3,13 @@ package initialize import ( "context" "encoding/json" + "github.com/perfect-panel/server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/logger" - - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/nodeMultiplier" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/nodeMultiplier" + "github.com/perfect-panel/server/pkg/tool" ) func Node(ctx *svc.ServiceContext) { @@ -19,9 +18,40 @@ func Node(ctx *svc.ServiceContext) { if err != nil { panic(err) } - var nodeConfig config.NodeConfig + var nodeConfig config.NodeDBConfig tool.SystemConfigSliceReflectToStruct(configs, &nodeConfig) - ctx.Config.Node = nodeConfig + c := config.NodeConfig{ + NodeSecret: nodeConfig.NodeSecret, + NodePullInterval: nodeConfig.NodePullInterval, + NodePushInterval: nodeConfig.NodePushInterval, + IPStrategy: nodeConfig.IPStrategy, + TrafficReportThreshold: nodeConfig.TrafficReportThreshold, + } + if nodeConfig.DNS != "" { + var dns []config.NodeDNS + err = json.Unmarshal([]byte(nodeConfig.DNS), &dns) + if err != nil { + logger.Errorf("[Node] Unmarshal DNS config error: %s", err.Error()) + panic(err) + } + c.DNS = dns + } + if nodeConfig.Block != "" { + var block []string + _ = json.Unmarshal([]byte(nodeConfig.Block), &block) + c.Block = tool.RemoveDuplicateElements(block...) + } + if nodeConfig.Outbound != "" { + var outbound []config.NodeOutbound + err = json.Unmarshal([]byte(nodeConfig.Outbound), &outbound) + if err != nil { + logger.Errorf("[Node] Unmarshal Outbound config error: %s", err.Error()) + panic(err) + } + c.Outbound = outbound + } + + ctx.Config.Node = c // Manager initialization if ctx.DB.Model(&system.System{}).Where("`key` = ?", "NodeMultiplierConfig").Find(&system.System{}).RowsAffected == 0 { @@ -39,7 +69,6 @@ func Node(ctx *svc.ServiceContext) { nodeMultiplierData, err := ctx.SystemModel.FindNodeMultiplierConfig(context.Background()) if err != nil { - logger.Error("Get Node Multiplier Config Error: ", logger.Field("error", err.Error())) return } diff --git a/initialize/oauth.go b/initialize/oauth.go index 70676ad..999d773 100644 --- a/initialize/oauth.go +++ b/initialize/oauth.go @@ -1,8 +1,8 @@ package initialize import ( - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/logger" ) func OAuth(svc *svc.ServiceContext) { diff --git a/initialize/register.go b/initialize/register.go index d6ce119..bbe44f9 100644 --- a/initialize/register.go +++ b/initialize/register.go @@ -3,11 +3,11 @@ package initialize import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/tool" ) func Register(ctx *svc.ServiceContext) { diff --git a/initialize/site.go b/initialize/site.go index 20c82ab..6cdbdbb 100644 --- a/initialize/site.go +++ b/initialize/site.go @@ -3,11 +3,11 @@ package initialize import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/tool" ) func Site(ctx *svc.ServiceContext) { diff --git a/initialize/statistics.go b/initialize/statistics.go deleted file mode 100644 index fcb81c7..0000000 --- a/initialize/statistics.go +++ /dev/null @@ -1,57 +0,0 @@ -package initialize - -import ( - "context" - "time" - - "github.com/perfect-panel/ppanel-server/internal/model/cache" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -func TrafficDataToRedis(svcCtx *svc.ServiceContext) { - ctx := context.Background() - // 统计昨天的节点流量数据排行榜前10 - nodeData, err := svcCtx.TrafficLogModel.TopServersTrafficByDay(ctx, time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day()-1, 0, 0, 0, 0, time.Local), 10) - if err != nil { - logger.Errorw("统计昨天的流量数据失败", logger.Field("error", err.Error())) - } - var nodeCacheData []cache.NodeTodayTrafficRank - for _, node := range nodeData { - serverInfo, err := svcCtx.ServerModel.FindOne(ctx, node.ServerId) - if err != nil { - logger.Errorw("查询节点信息失败", logger.Field("error", err.Error())) - continue - } - nodeCacheData = append(nodeCacheData, cache.NodeTodayTrafficRank{ - ID: node.ServerId, - Name: serverInfo.Name, - Upload: node.Upload, - Download: node.Download, - Total: node.Upload + node.Download, - }) - } - // 写入缓存 - if err = svcCtx.NodeCache.UpdateYesterdayNodeTotalTrafficRank(ctx, nodeCacheData); err != nil { - logger.Errorw("写入昨天的流量数据到缓存失败", logger.Field("error", err.Error())) - } - // 统计昨天的用户流量数据排行榜前10 - userData, err := svcCtx.TrafficLogModel.TopUsersTrafficByDay(ctx, time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day()-1, 0, 0, 0, 0, time.Local), 10) - if err != nil { - logger.Errorw("统计昨天的流量数据失败", logger.Field("error", err.Error())) - } - var userCacheData []cache.UserTodayTrafficRank - for _, user := range userData { - userCacheData = append(userCacheData, cache.UserTodayTrafficRank{ - SID: user.SubscribeId, - Upload: user.Upload, - Download: user.Download, - Total: user.Upload + user.Download, - }) - } - // 写入缓存 - if err = svcCtx.NodeCache.UpdateYesterdayUserTotalTrafficRank(ctx, userCacheData); err != nil { - logger.Errorw("写入昨天的流量数据到缓存失败", logger.Field("error", err.Error())) - } - logger.Infow("初始化昨天的流量数据到缓存成功") -} diff --git a/initialize/subscribe.go b/initialize/subscribe.go index a584a3d..7c5f2fe 100644 --- a/initialize/subscribe.go +++ b/initialize/subscribe.go @@ -3,11 +3,11 @@ package initialize import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/tool" ) func Subscribe(svc *svc.ServiceContext) { diff --git a/initialize/telegram.go b/initialize/telegram.go index 3a2e3a8..56c7086 100644 --- a/initialize/telegram.go +++ b/initialize/telegram.go @@ -4,14 +4,14 @@ import ( "context" "fmt" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/logic/telegram" - "github.com/perfect-panel/ppanel-server/internal/model/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/logic/telegram" + "github.com/perfect-panel/server/internal/model/auth" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/tool" ) func Telegram(svc *svc.ServiceContext) { diff --git a/initialize/verify.go b/initialize/verify.go index c7a2f1a..965e021 100644 --- a/initialize/verify.go +++ b/initialize/verify.go @@ -3,11 +3,11 @@ package initialize import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/tool" ) type verifyConfig struct { diff --git a/initialize/version.go b/initialize/version.go index fbabfac..9eac7d4 100644 --- a/initialize/version.go +++ b/initialize/version.go @@ -1,127 +1,45 @@ package initialize import ( - "fmt" - - "github.com/perfect-panel/ppanel-server/pkg/logger" + "errors" + "github.com/perfect-panel/server/internal/model/user" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/initialize/migrate/patch" - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/initialize/migrate" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/orm" ) -func VerifyVersion(ctx *svc.ServiceContext) { - var configVersion system.System - err := ctx.DB.Transaction(func(db *gorm.DB) error { - db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").First(&configVersion) - if configVersion.Value != constant.Version { - // Version eg: 1.0.0(10000) - current := tool.ExtractVersionNumber(constant.Version) - sqlVersion := tool.ExtractVersionNumber(configVersion.Value) - logger.Infof("Verify System Version, current version: %d, datebase version: %d", current, sqlVersion) - if current > sqlVersion { - // Migrate to Milestone Version - // - // Migrate SQL to 0.1.7(01703) - if sqlVersion < 1705 { - if err := migrate01701(db, sqlVersion); err != nil { - return err - } - } - // 重新执行2000版本的迁移 - if sqlVersion == 2002 { - sqlVersion = 2000 - } - // Migrate SQL to 0.2.0(02000) - if sqlVersion < 2009 { - if err := migrate02000(db, sqlVersion); err != nil { - return err - } - } - // Migrate SQL to 0.3.0(03000) - if sqlVersion < 3002 { - if err := migrate03000(db, sqlVersion); err != nil { - return err - } - } - +func Migrate(ctx *svc.ServiceContext) { + mc := orm.Mysql{ + Config: ctx.Config.MySQL, + } + if err := migrate.Migrate(mc.Dsn()).Up(); err != nil { + if errors.Is(err, migrate.NoChange) { + logger.Info("[Migrate] database not change") + return + } + logger.Errorf("[Migrate] Up error: %v", err.Error()) + panic(err) + } + // if not found admin user + err := ctx.DB.Transaction(func(tx *gorm.DB) error { + var count int64 + if err := tx.Model(&user.User{}).Count(&count).Error; err != nil { + return err + } + if count == 0 { + if err := migrate.CreateAdminUser(ctx.Config.Administrator.Email, ctx.Config.Administrator.Password, tx); err != nil { + logger.Errorf("[Migrate] CreateAdminUser error: %v", err.Error()) + return err } + logger.Info("[Migrate] Create admin user success") } return nil }) if err != nil { - panic("update system version error:" + err.Error()) + panic(err) } } -func migrate01701(db *gorm.DB, sqlVersion int) error { - migrations := map[int]func(*gorm.DB) error{ - 1200: patch.Migrate01200, - 1201: patch.Migrate01201, - 1202: patch.Migrate01202, - 1203: patch.Migrate01203, - 1204: patch.Migrate01204, - 1205: patch.Migrate01205, - 1301: patch.Migrate01301, - 1602: patch.Migrate01602, - 1701: patch.Migrate01701, - 1702: patch.Migrate01702, - 1703: patch.Migrate01703, - 1704: patch.Migrate01704, - 1705: patch.Migrate01705, - } - - for v, migrate := range migrations { - if sqlVersion < v { - if err := migrate(db); err != nil { - return fmt.Errorf("migrator %d version error: %w", v, err) - } - logger.Infof(fmt.Sprintf("Migrate %d version success", v)) - } - } - return nil -} - -func migrate02000(db *gorm.DB, sqlVersion int) error { - migrations := map[int]func(*gorm.DB) error{ - 2000: patch.Migrate02000, - 2001: patch.Migrate02001, - 2002: patch.Migrate02002, - 2003: patch.Migrate02003, - 2007: patch.Migrate02007, - 2008: patch.Migrate02008, - 2009: patch.Migrate02009, - 2010: patch.Migrate02010, - 2011: patch.Migrate02011, - } - - for v, migrate := range migrations { - if sqlVersion < v { - if err := migrate(db); err != nil { - return fmt.Errorf("migrator %d version error: %w", v, err) - } - logger.Infof(fmt.Sprintf("Migrate %d version success", v)) - } - } - return nil -} - -func migrate03000(db *gorm.DB, sqlVersion int) error { - migrations := map[int]func(*gorm.DB) error{ - 3001: patch.Migrate03001, - 3002: patch.Migrate03002, - } - - for v, migrate := range migrations { - if sqlVersion < v { - if err := migrate(db); err != nil { - return fmt.Errorf("migrator %d version error: %w", v, err) - } - logger.Infof(fmt.Sprintf("Migrate %d version success", v)) - } - } - return nil -} diff --git a/internal/config/cacheKey.go b/internal/config/cacheKey.go index 02f5be9..655ce55 100644 --- a/internal/config/cacheKey.go +++ b/internal/config/cacheKey.go @@ -12,9 +12,6 @@ const SiteConfigKey = "system:site_config" // SubscribeConfigKey Subscribe Config Key const SubscribeConfigKey = "system:subscribe_config" -// ApplicationKey Application Key -const ApplicationKey = "system:application" - // RegisterConfigKey Register Config Key const RegisterConfigKey = "system:register_config" @@ -51,26 +48,12 @@ const AuthCodeCacheKey = "auth:verify:email" // AuthCodeTelephoneCacheKey Register Code Cache Key const AuthCodeTelephoneCacheKey = "auth:verify:telephone" -// ServerUserListCacheKey Server User List Cache Key -const ServerUserListCacheKey = "server:user_list:id:" - -// ServerConfigCacheKey Server Config Cache Key -const ServerConfigCacheKey = "server:config:id:" - -// CommonStat Cache Key +// CommonStatCacheKey CommonStat Cache Key const CommonStatCacheKey = "common:stat" -// ServerStatusCacheKey Server Status Cache Key -const ServerStatusCacheKey = "server:status:id:" - // ServerCountCacheKey Server Count Cache Key const ServerCountCacheKey = "server:count" -// UserBindTelegramCacheKey User Bind Telegram Cache Key -const UserBindTelegramCacheKey = "user:bind:telegram:code:" - -const CacheSmsCount = "cache:sms:count" - // SendIntervalKeyPrefix Auth Code Send Interval Key Prefix const SendIntervalKeyPrefix = "send:interval:" diff --git a/internal/config/config.go b/internal/config/config.go index 824d656..59ece74 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,8 +1,10 @@ package config import ( - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/orm" + "encoding/json" + + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/orm" ) type Config struct { @@ -19,12 +21,14 @@ type Config struct { Node NodeConfig `yaml:"Node"` Mobile MobileConfig `yaml:"Mobile"` Email EmailConfig `yaml:"Email"` + Device DeviceConfig `yaml:"device"` Verify Verify `yaml:"Verify"` VerifyCode VerifyCode `yaml:"VerifyCode"` Register RegisterConfig `yaml:"Register"` Subscribe SubscribeConfig `yaml:"Subscribe"` Invite InviteConfig `yaml:"Invite"` Telegram Telegram `yaml:"Telegram"` + Log Log `yaml:"Log"` Administrator struct { Email string `yaml:"Email" default:"admin@ppanel.dev"` Password string `yaml:"Password" default:"password"` @@ -52,9 +56,11 @@ type Verify struct { type SubscribeConfig struct { SingleModel bool `yaml:"SingleModel" default:"false"` - SubscribePath string `yaml:"SubscribePath" default:"/api/subscribe"` + SubscribePath string `yaml:"SubscribePath" default:"/v1/subscribe/config"` SubscribeDomain string `yaml:"SubscribeDomain" default:""` PanDomain bool `yaml:"PanDomain" default:"false"` + UserAgentLimit bool `yaml:"UserAgentLimit" default:"false"` + UserAgentList string `yaml:"UserAgentList" default:""` } type RegisterConfig struct { @@ -91,6 +97,14 @@ type MobileConfig struct { 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 { Host string `yaml:"Host" default:""` SiteName string `yaml:"SiteName" default:""` @@ -102,9 +116,76 @@ type SiteConfig struct { } type NodeConfig struct { - NodeSecret string `yaml:"NodeSecret" default:""` - NodePullInterval int64 `yaml:"NodePullInterval" default:"60"` - NodePushInterval int64 `yaml:"NodePushInterval" default:"60"` + NodeSecret string `yaml:"NodeSecret" default:""` + NodePullInterval int64 `yaml:"NodePullInterval" default:"60"` + NodePushInterval int64 `yaml:"NodePushInterval" default:"60"` + TrafficReportThreshold int64 `yaml:"TrafficReportThreshold" default:"0"` + IPStrategy string `yaml:"IPStrategy" default:""` + DNS []NodeDNS `yaml:"DNS"` + Block []string `yaml:"Block" ` + Outbound []NodeOutbound `yaml:"Outbound"` +} + +func (n *NodeConfig) Marshal() ([]byte, error) { + type Alias NodeConfig + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(n), + }) +} + +func (n *NodeConfig) Unmarshal(data []byte) error { + type Alias NodeConfig + aux := &struct { + *Alias + }{ + Alias: (*Alias)(n), + } + return json.Unmarshal(data, &aux) +} + +type NodeDNS struct { + Proto string `json:"proto"` + Address string `json:"address"` + Domains []string `json:"domains"` +} + +func (n *NodeDNS) Marshal() ([]byte, error) { + type Alias NodeDNS + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(n), + }) +} + +func (n *NodeDNS) Unmarshal(data []byte) error { + type Alias NodeDNS + aux := &struct { + *Alias + }{ + Alias: (*Alias)(n), + } + return json.Unmarshal(data, &aux) +} + +type NodeOutbound struct { + Name string `json:"name"` + Protocol string `json:"protocol"` + Address string `json:"address"` + Port int64 `json:"port"` + Password string `json:"password"` + Rules []string `json:"rules"` +} + +func (n *NodeOutbound) Marshal() ([]byte, error) { + type Alias NodeOutbound + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(n), + }) } type File struct { @@ -144,3 +225,19 @@ type VerifyCode struct { Limit int64 `yaml:"Limit" default:"15"` Interval int64 `yaml:"Interval" default:"60"` } + +type Log struct { + AutoClear bool `yaml:"AutoClear" default:"true"` + ClearDays int64 `yaml:"ClearDays" default:"7"` +} + +type NodeDBConfig struct { + NodeSecret string + NodePullInterval int64 + NodePushInterval int64 + TrafficReportThreshold int64 + IPStrategy string + DNS string + Block string + Outbound string +} diff --git a/internal/config/constant.go b/internal/config/constant.go deleted file mode 100644 index 3f7625e..0000000 --- a/internal/config/constant.go +++ /dev/null @@ -1,5 +0,0 @@ -package config - -import "github.com/perfect-panel/ppanel-server/pkg/constant" - -const Version = constant.Version diff --git a/internal/handler/admin/ads/createAdsHandler.go b/internal/handler/admin/ads/createAdsHandler.go index d06a5ed..5d45005 100644 --- a/internal/handler/admin/ads/createAdsHandler.go +++ b/internal/handler/admin/ads/createAdsHandler.go @@ -2,10 +2,10 @@ package ads import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/ads" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/ads" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Create Ads diff --git a/internal/handler/admin/ads/deleteAdsHandler.go b/internal/handler/admin/ads/deleteAdsHandler.go index efb153d..e8a804b 100644 --- a/internal/handler/admin/ads/deleteAdsHandler.go +++ b/internal/handler/admin/ads/deleteAdsHandler.go @@ -2,10 +2,10 @@ package ads import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/ads" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/ads" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Delete Ads diff --git a/internal/handler/admin/ads/getAdsDetailHandler.go b/internal/handler/admin/ads/getAdsDetailHandler.go index 3c3eb4d..bce6e38 100644 --- a/internal/handler/admin/ads/getAdsDetailHandler.go +++ b/internal/handler/admin/ads/getAdsDetailHandler.go @@ -2,10 +2,10 @@ package ads import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/ads" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/ads" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get Ads Detail diff --git a/internal/handler/admin/ads/getAdsListHandler.go b/internal/handler/admin/ads/getAdsListHandler.go index e2dc1ec..92ff173 100644 --- a/internal/handler/admin/ads/getAdsListHandler.go +++ b/internal/handler/admin/ads/getAdsListHandler.go @@ -2,10 +2,10 @@ package ads import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/ads" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/ads" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get Ads List diff --git a/internal/handler/admin/ads/updateAdsHandler.go b/internal/handler/admin/ads/updateAdsHandler.go index 801365b..8b1d28c 100644 --- a/internal/handler/admin/ads/updateAdsHandler.go +++ b/internal/handler/admin/ads/updateAdsHandler.go @@ -2,10 +2,10 @@ package ads import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/ads" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/ads" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update Ads diff --git a/internal/handler/admin/announcement/createAnnouncementHandler.go b/internal/handler/admin/announcement/createAnnouncementHandler.go index 138b23c..573da5f 100644 --- a/internal/handler/admin/announcement/createAnnouncementHandler.go +++ b/internal/handler/admin/announcement/createAnnouncementHandler.go @@ -2,10 +2,10 @@ package announcement import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/announcement" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/announcement" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Create announcement diff --git a/internal/handler/admin/announcement/deleteAnnouncementHandler.go b/internal/handler/admin/announcement/deleteAnnouncementHandler.go index ae673d0..2cbb88e 100644 --- a/internal/handler/admin/announcement/deleteAnnouncementHandler.go +++ b/internal/handler/admin/announcement/deleteAnnouncementHandler.go @@ -2,10 +2,10 @@ package announcement import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/announcement" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/announcement" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Delete announcement diff --git a/internal/handler/admin/announcement/getAnnouncementHandler.go b/internal/handler/admin/announcement/getAnnouncementHandler.go index ea51c64..4525af6 100644 --- a/internal/handler/admin/announcement/getAnnouncementHandler.go +++ b/internal/handler/admin/announcement/getAnnouncementHandler.go @@ -2,10 +2,10 @@ package announcement import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/announcement" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/announcement" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get announcement diff --git a/internal/handler/admin/announcement/getAnnouncementListHandler.go b/internal/handler/admin/announcement/getAnnouncementListHandler.go index b5c0f55..64288b1 100644 --- a/internal/handler/admin/announcement/getAnnouncementListHandler.go +++ b/internal/handler/admin/announcement/getAnnouncementListHandler.go @@ -2,10 +2,10 @@ package announcement import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/announcement" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/announcement" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get announcement list diff --git a/internal/handler/admin/announcement/updateAnnouncementHandler.go b/internal/handler/admin/announcement/updateAnnouncementHandler.go index 96be68c..c6a82c2 100644 --- a/internal/handler/admin/announcement/updateAnnouncementHandler.go +++ b/internal/handler/admin/announcement/updateAnnouncementHandler.go @@ -2,10 +2,10 @@ package announcement import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/announcement" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/announcement" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update announcement diff --git a/internal/handler/admin/application/createSubscribeApplicationHandler.go b/internal/handler/admin/application/createSubscribeApplicationHandler.go new file mode 100644 index 0000000..6f9d136 --- /dev/null +++ b/internal/handler/admin/application/createSubscribeApplicationHandler.go @@ -0,0 +1,26 @@ +package application + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/application" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Create subscribe application +func CreateSubscribeApplicationHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.CreateSubscribeApplicationRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := application.NewCreateSubscribeApplicationLogic(c.Request.Context(), svcCtx) + resp, err := l.CreateSubscribeApplication(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/application/deleteSubscribeApplicationHandler.go b/internal/handler/admin/application/deleteSubscribeApplicationHandler.go new file mode 100644 index 0000000..6e298a5 --- /dev/null +++ b/internal/handler/admin/application/deleteSubscribeApplicationHandler.go @@ -0,0 +1,26 @@ +package application + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/application" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Delete subscribe application +func DeleteSubscribeApplicationHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.DeleteSubscribeApplicationRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := application.NewDeleteSubscribeApplicationLogic(c.Request.Context(), svcCtx) + err := l.DeleteSubscribeApplication(&req) + result.HttpResult(c, nil, err) + } +} diff --git a/internal/handler/admin/application/getSubscribeApplicationListHandler.go b/internal/handler/admin/application/getSubscribeApplicationListHandler.go new file mode 100644 index 0000000..5e8222d --- /dev/null +++ b/internal/handler/admin/application/getSubscribeApplicationListHandler.go @@ -0,0 +1,26 @@ +package application + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/application" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Get subscribe application list +func GetSubscribeApplicationListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.GetSubscribeApplicationListRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := application.NewGetSubscribeApplicationListLogic(c.Request.Context(), svcCtx) + resp, err := l.GetSubscribeApplicationList(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/application/previewSubscribeTemplateHandler.go b/internal/handler/admin/application/previewSubscribeTemplateHandler.go new file mode 100644 index 0000000..6c136d2 --- /dev/null +++ b/internal/handler/admin/application/previewSubscribeTemplateHandler.go @@ -0,0 +1,27 @@ +package application + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/application" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Preview Template +func PreviewSubscribeTemplateHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.PreviewSubscribeTemplateRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := application.NewPreviewSubscribeTemplateLogic(c.Request.Context(), svcCtx) + resp, err := l.PreviewSubscribeTemplate(&req) + result.HttpResult(c, resp, err) + + } +} diff --git a/internal/handler/admin/application/updateSubscribeApplicationHandler.go b/internal/handler/admin/application/updateSubscribeApplicationHandler.go new file mode 100644 index 0000000..beaf306 --- /dev/null +++ b/internal/handler/admin/application/updateSubscribeApplicationHandler.go @@ -0,0 +1,26 @@ +package application + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/application" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Update subscribe application +func UpdateSubscribeApplicationHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.UpdateSubscribeApplicationRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := application.NewUpdateSubscribeApplicationLogic(c.Request.Context(), svcCtx) + resp, err := l.UpdateSubscribeApplication(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/authMethod/getAuthMethodConfigHandler.go b/internal/handler/admin/authMethod/getAuthMethodConfigHandler.go index ac94fc1..6b63e8d 100644 --- a/internal/handler/admin/authMethod/getAuthMethodConfigHandler.go +++ b/internal/handler/admin/authMethod/getAuthMethodConfigHandler.go @@ -2,10 +2,10 @@ package authMethod import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/authMethod" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/authMethod" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get auth method config diff --git a/internal/handler/admin/authMethod/getAuthMethodListHandler.go b/internal/handler/admin/authMethod/getAuthMethodListHandler.go index 9e20e2f..93a64de 100644 --- a/internal/handler/admin/authMethod/getAuthMethodListHandler.go +++ b/internal/handler/admin/authMethod/getAuthMethodListHandler.go @@ -2,9 +2,9 @@ package authMethod import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/authMethod" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/authMethod" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get auth method list diff --git a/internal/handler/admin/authMethod/getEmailPlatformHandler.go b/internal/handler/admin/authMethod/getEmailPlatformHandler.go index 97a2aba..c5a751e 100644 --- a/internal/handler/admin/authMethod/getEmailPlatformHandler.go +++ b/internal/handler/admin/authMethod/getEmailPlatformHandler.go @@ -2,9 +2,9 @@ package authMethod import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/authMethod" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/authMethod" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get email support platform diff --git a/internal/handler/admin/authMethod/getSmsPlatformHandler.go b/internal/handler/admin/authMethod/getSmsPlatformHandler.go index 15dcd91..092c7b7 100644 --- a/internal/handler/admin/authMethod/getSmsPlatformHandler.go +++ b/internal/handler/admin/authMethod/getSmsPlatformHandler.go @@ -2,9 +2,9 @@ package authMethod import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/authMethod" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/authMethod" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get sms support platform diff --git a/internal/handler/admin/authMethod/testEmailSendHandler.go b/internal/handler/admin/authMethod/testEmailSendHandler.go index fd98afd..ed91244 100644 --- a/internal/handler/admin/authMethod/testEmailSendHandler.go +++ b/internal/handler/admin/authMethod/testEmailSendHandler.go @@ -2,10 +2,10 @@ package authMethod import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/authMethod" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/authMethod" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Test email send diff --git a/internal/handler/admin/authMethod/testSmsSendHandler.go b/internal/handler/admin/authMethod/testSmsSendHandler.go index 2bc551e..b74edc4 100644 --- a/internal/handler/admin/authMethod/testSmsSendHandler.go +++ b/internal/handler/admin/authMethod/testSmsSendHandler.go @@ -2,10 +2,10 @@ package authMethod import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/authMethod" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/authMethod" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Test sms send diff --git a/internal/handler/admin/authMethod/updateAuthMethodConfigHandler.go b/internal/handler/admin/authMethod/updateAuthMethodConfigHandler.go index 0af525e..af2b9e2 100644 --- a/internal/handler/admin/authMethod/updateAuthMethodConfigHandler.go +++ b/internal/handler/admin/authMethod/updateAuthMethodConfigHandler.go @@ -2,10 +2,10 @@ package authMethod import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/authMethod" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/authMethod" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update auth method config diff --git a/internal/handler/admin/console/queryRevenueStatisticsHandler.go b/internal/handler/admin/console/queryRevenueStatisticsHandler.go index 2894000..d9c9c89 100644 --- a/internal/handler/admin/console/queryRevenueStatisticsHandler.go +++ b/internal/handler/admin/console/queryRevenueStatisticsHandler.go @@ -2,9 +2,9 @@ package console import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/console" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/console" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Query revenue statistics diff --git a/internal/handler/admin/console/queryServerTotalDataHandler.go b/internal/handler/admin/console/queryServerTotalDataHandler.go index d646cc6..1f88689 100644 --- a/internal/handler/admin/console/queryServerTotalDataHandler.go +++ b/internal/handler/admin/console/queryServerTotalDataHandler.go @@ -2,9 +2,9 @@ package console import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/console" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/console" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Query server total data diff --git a/internal/handler/admin/console/queryTicketWaitReplyHandler.go b/internal/handler/admin/console/queryTicketWaitReplyHandler.go index b39a61e..5aa6f4f 100644 --- a/internal/handler/admin/console/queryTicketWaitReplyHandler.go +++ b/internal/handler/admin/console/queryTicketWaitReplyHandler.go @@ -2,9 +2,9 @@ package console import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/console" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/console" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Query ticket wait reply diff --git a/internal/handler/admin/console/queryUserStatisticsHandler.go b/internal/handler/admin/console/queryUserStatisticsHandler.go index 1d651b3..1d738b1 100644 --- a/internal/handler/admin/console/queryUserStatisticsHandler.go +++ b/internal/handler/admin/console/queryUserStatisticsHandler.go @@ -2,9 +2,9 @@ package console import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/console" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/console" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Query user statistics diff --git a/internal/handler/admin/coupon/batchDeleteCouponHandler.go b/internal/handler/admin/coupon/batchDeleteCouponHandler.go index cbb6b0c..60f3035 100644 --- a/internal/handler/admin/coupon/batchDeleteCouponHandler.go +++ b/internal/handler/admin/coupon/batchDeleteCouponHandler.go @@ -2,10 +2,10 @@ package coupon import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/coupon" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/coupon" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Batch delete coupon diff --git a/internal/handler/admin/coupon/createCouponHandler.go b/internal/handler/admin/coupon/createCouponHandler.go index baf9c7a..6a48d68 100644 --- a/internal/handler/admin/coupon/createCouponHandler.go +++ b/internal/handler/admin/coupon/createCouponHandler.go @@ -2,10 +2,10 @@ package coupon import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/coupon" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/coupon" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Create coupon diff --git a/internal/handler/admin/coupon/deleteCouponHandler.go b/internal/handler/admin/coupon/deleteCouponHandler.go index 651de28..94c59bc 100644 --- a/internal/handler/admin/coupon/deleteCouponHandler.go +++ b/internal/handler/admin/coupon/deleteCouponHandler.go @@ -2,10 +2,10 @@ package coupon import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/coupon" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/coupon" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Delete coupon diff --git a/internal/handler/admin/coupon/getCouponListHandler.go b/internal/handler/admin/coupon/getCouponListHandler.go index ce0b1d0..b8d0d96 100644 --- a/internal/handler/admin/coupon/getCouponListHandler.go +++ b/internal/handler/admin/coupon/getCouponListHandler.go @@ -2,10 +2,10 @@ package coupon import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/coupon" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/coupon" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get coupon list diff --git a/internal/handler/admin/coupon/updateCouponHandler.go b/internal/handler/admin/coupon/updateCouponHandler.go index 3443553..412653a 100644 --- a/internal/handler/admin/coupon/updateCouponHandler.go +++ b/internal/handler/admin/coupon/updateCouponHandler.go @@ -2,10 +2,10 @@ package coupon import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/coupon" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/coupon" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update coupon diff --git a/internal/handler/admin/document/batchDeleteDocumentHandler.go b/internal/handler/admin/document/batchDeleteDocumentHandler.go index 490125c..204e499 100644 --- a/internal/handler/admin/document/batchDeleteDocumentHandler.go +++ b/internal/handler/admin/document/batchDeleteDocumentHandler.go @@ -2,10 +2,10 @@ package document import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/document" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/document" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Batch delete document diff --git a/internal/handler/admin/document/createDocumentHandler.go b/internal/handler/admin/document/createDocumentHandler.go index 08d0fcb..e7f8a72 100644 --- a/internal/handler/admin/document/createDocumentHandler.go +++ b/internal/handler/admin/document/createDocumentHandler.go @@ -2,10 +2,10 @@ package document import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/document" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/document" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Create document diff --git a/internal/handler/admin/document/deleteDocumentHandler.go b/internal/handler/admin/document/deleteDocumentHandler.go index 6dd5a7b..45b0f10 100644 --- a/internal/handler/admin/document/deleteDocumentHandler.go +++ b/internal/handler/admin/document/deleteDocumentHandler.go @@ -2,10 +2,10 @@ package document import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/document" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/document" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Delete document diff --git a/internal/handler/admin/document/getDocumentDetailHandler.go b/internal/handler/admin/document/getDocumentDetailHandler.go index f272f12..7def525 100644 --- a/internal/handler/admin/document/getDocumentDetailHandler.go +++ b/internal/handler/admin/document/getDocumentDetailHandler.go @@ -2,10 +2,10 @@ package document import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/document" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/document" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get document detail diff --git a/internal/handler/admin/document/getDocumentListHandler.go b/internal/handler/admin/document/getDocumentListHandler.go index 48a3e62..c3290f5 100644 --- a/internal/handler/admin/document/getDocumentListHandler.go +++ b/internal/handler/admin/document/getDocumentListHandler.go @@ -2,10 +2,10 @@ package document import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/document" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/document" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get document list diff --git a/internal/handler/admin/document/updateDocumentHandler.go b/internal/handler/admin/document/updateDocumentHandler.go index b88798b..b478e45 100644 --- a/internal/handler/admin/document/updateDocumentHandler.go +++ b/internal/handler/admin/document/updateDocumentHandler.go @@ -2,10 +2,10 @@ package document import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/document" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/document" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update document diff --git a/internal/handler/admin/log/filterBalanceLogHandler.go b/internal/handler/admin/log/filterBalanceLogHandler.go new file mode 100644 index 0000000..c8bf7d1 --- /dev/null +++ b/internal/handler/admin/log/filterBalanceLogHandler.go @@ -0,0 +1,26 @@ +package log + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Filter balance log +func FilterBalanceLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.FilterBalanceLogRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := log.NewFilterBalanceLogLogic(c.Request.Context(), svcCtx) + resp, err := l.FilterBalanceLog(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/log/filterCommissionLogHandler.go b/internal/handler/admin/log/filterCommissionLogHandler.go new file mode 100644 index 0000000..07361cd --- /dev/null +++ b/internal/handler/admin/log/filterCommissionLogHandler.go @@ -0,0 +1,26 @@ +package log + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Filter commission log +func FilterCommissionLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.FilterCommissionLogRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := log.NewFilterCommissionLogLogic(c.Request.Context(), svcCtx) + resp, err := l.FilterCommissionLog(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/log/filterEmailLogHandler.go b/internal/handler/admin/log/filterEmailLogHandler.go new file mode 100644 index 0000000..6d9f03d --- /dev/null +++ b/internal/handler/admin/log/filterEmailLogHandler.go @@ -0,0 +1,26 @@ +package log + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Filter email log +func FilterEmailLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.FilterLogParams + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := log.NewFilterEmailLogLogic(c.Request.Context(), svcCtx) + resp, err := l.FilterEmailLog(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/log/filterGiftLogHandler.go b/internal/handler/admin/log/filterGiftLogHandler.go new file mode 100644 index 0000000..e650a27 --- /dev/null +++ b/internal/handler/admin/log/filterGiftLogHandler.go @@ -0,0 +1,26 @@ +package log + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Filter gift log +func FilterGiftLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.FilterGiftLogRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := log.NewFilterGiftLogLogic(c.Request.Context(), svcCtx) + resp, err := l.FilterGiftLog(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/log/filterLoginLogHandler.go b/internal/handler/admin/log/filterLoginLogHandler.go new file mode 100644 index 0000000..c2dae41 --- /dev/null +++ b/internal/handler/admin/log/filterLoginLogHandler.go @@ -0,0 +1,26 @@ +package log + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Filter login log +func FilterLoginLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.FilterLoginLogRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := log.NewFilterLoginLogLogic(c.Request.Context(), svcCtx) + resp, err := l.FilterLoginLog(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/log/filterMobileLogHandler.go b/internal/handler/admin/log/filterMobileLogHandler.go new file mode 100644 index 0000000..0d45e27 --- /dev/null +++ b/internal/handler/admin/log/filterMobileLogHandler.go @@ -0,0 +1,26 @@ +package log + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Filter mobile log +func FilterMobileLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.FilterLogParams + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := log.NewFilterMobileLogLogic(c.Request.Context(), svcCtx) + resp, err := l.FilterMobileLog(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/log/filterRegisterLogHandler.go b/internal/handler/admin/log/filterRegisterLogHandler.go new file mode 100644 index 0000000..a5ca9e8 --- /dev/null +++ b/internal/handler/admin/log/filterRegisterLogHandler.go @@ -0,0 +1,26 @@ +package log + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Filter register log +func FilterRegisterLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.FilterRegisterLogRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := log.NewFilterRegisterLogLogic(c.Request.Context(), svcCtx) + resp, err := l.FilterRegisterLog(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/log/filterResetSubscribeLogHandler.go b/internal/handler/admin/log/filterResetSubscribeLogHandler.go new file mode 100644 index 0000000..f4d96e5 --- /dev/null +++ b/internal/handler/admin/log/filterResetSubscribeLogHandler.go @@ -0,0 +1,26 @@ +package log + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Filter reset subscribe log +func FilterResetSubscribeLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.FilterResetSubscribeLogRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := log.NewFilterResetSubscribeLogLogic(c.Request.Context(), svcCtx) + resp, err := l.FilterResetSubscribeLog(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/log/filterServerTrafficLogHandler.go b/internal/handler/admin/log/filterServerTrafficLogHandler.go new file mode 100644 index 0000000..ec522ed --- /dev/null +++ b/internal/handler/admin/log/filterServerTrafficLogHandler.go @@ -0,0 +1,26 @@ +package log + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Filter server traffic log +func FilterServerTrafficLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.FilterServerTrafficLogRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := log.NewFilterServerTrafficLogLogic(c.Request.Context(), svcCtx) + resp, err := l.FilterServerTrafficLog(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/log/filterSubscribeLogHandler.go b/internal/handler/admin/log/filterSubscribeLogHandler.go new file mode 100644 index 0000000..f01b61e --- /dev/null +++ b/internal/handler/admin/log/filterSubscribeLogHandler.go @@ -0,0 +1,26 @@ +package log + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Filter subscribe log +func FilterSubscribeLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.FilterSubscribeLogRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := log.NewFilterSubscribeLogLogic(c.Request.Context(), svcCtx) + resp, err := l.FilterSubscribeLog(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/log/filterTrafficLogDetailsHandler.go b/internal/handler/admin/log/filterTrafficLogDetailsHandler.go new file mode 100644 index 0000000..c77a881 --- /dev/null +++ b/internal/handler/admin/log/filterTrafficLogDetailsHandler.go @@ -0,0 +1,26 @@ +package log + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Filter traffic log details +func FilterTrafficLogDetailsHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.FilterTrafficLogDetailsRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := log.NewFilterTrafficLogDetailsLogic(c.Request.Context(), svcCtx) + resp, err := l.FilterTrafficLogDetails(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/log/filterUserSubscribeTrafficLogHandler.go b/internal/handler/admin/log/filterUserSubscribeTrafficLogHandler.go new file mode 100644 index 0000000..976e278 --- /dev/null +++ b/internal/handler/admin/log/filterUserSubscribeTrafficLogHandler.go @@ -0,0 +1,26 @@ +package log + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Filter user subscribe traffic log +func FilterUserSubscribeTrafficLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.FilterSubscribeTrafficRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := log.NewFilterUserSubscribeTrafficLogLogic(c.Request.Context(), svcCtx) + resp, err := l.FilterUserSubscribeTrafficLog(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/log/getLogSettingHandler.go b/internal/handler/admin/log/getLogSettingHandler.go new file mode 100644 index 0000000..50217cb --- /dev/null +++ b/internal/handler/admin/log/getLogSettingHandler.go @@ -0,0 +1,18 @@ +package log + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" +) + +// Get log setting +func GetLogSettingHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + + l := log.NewGetLogSettingLogic(c.Request.Context(), svcCtx) + resp, err := l.GetLogSetting() + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/log/getMessageLogListHandler.go b/internal/handler/admin/log/getMessageLogListHandler.go index ab535a2..7b0dd3c 100644 --- a/internal/handler/admin/log/getMessageLogListHandler.go +++ b/internal/handler/admin/log/getMessageLogListHandler.go @@ -2,10 +2,10 @@ package log import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/log" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get message log list diff --git a/internal/handler/admin/log/updateLogSettingHandler.go b/internal/handler/admin/log/updateLogSettingHandler.go new file mode 100644 index 0000000..91aa2c8 --- /dev/null +++ b/internal/handler/admin/log/updateLogSettingHandler.go @@ -0,0 +1,26 @@ +package log + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Update log setting +func UpdateLogSettingHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.LogSetting + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := log.NewUpdateLogSettingLogic(c.Request.Context(), svcCtx) + err := l.UpdateLogSetting(&req) + result.HttpResult(c, nil, err) + } +} diff --git a/internal/handler/admin/marketing/createBatchSendEmailTaskHandler.go b/internal/handler/admin/marketing/createBatchSendEmailTaskHandler.go new file mode 100644 index 0000000..8057689 --- /dev/null +++ b/internal/handler/admin/marketing/createBatchSendEmailTaskHandler.go @@ -0,0 +1,26 @@ +package marketing + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/marketing" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Create a batch send email task +func CreateBatchSendEmailTaskHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.CreateBatchSendEmailTaskRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := marketing.NewCreateBatchSendEmailTaskLogic(c.Request.Context(), svcCtx) + err := l.CreateBatchSendEmailTask(&req) + result.HttpResult(c, nil, err) + } +} diff --git a/internal/handler/admin/marketing/createQuotaTaskHandler.go b/internal/handler/admin/marketing/createQuotaTaskHandler.go new file mode 100644 index 0000000..0fb088b --- /dev/null +++ b/internal/handler/admin/marketing/createQuotaTaskHandler.go @@ -0,0 +1,26 @@ +package marketing + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/marketing" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Create a quota task +func CreateQuotaTaskHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.CreateQuotaTaskRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := marketing.NewCreateQuotaTaskLogic(c.Request.Context(), svcCtx) + err := l.CreateQuotaTask(&req) + result.HttpResult(c, nil, err) + } +} diff --git a/internal/handler/admin/marketing/getBatchSendEmailTaskListHandler.go b/internal/handler/admin/marketing/getBatchSendEmailTaskListHandler.go new file mode 100644 index 0000000..9c13f9f --- /dev/null +++ b/internal/handler/admin/marketing/getBatchSendEmailTaskListHandler.go @@ -0,0 +1,26 @@ +package marketing + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/marketing" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Get batch send email task list +func GetBatchSendEmailTaskListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.GetBatchSendEmailTaskListRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := marketing.NewGetBatchSendEmailTaskListLogic(c.Request.Context(), svcCtx) + resp, err := l.GetBatchSendEmailTaskList(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/marketing/getBatchSendEmailTaskStatusHandler.go b/internal/handler/admin/marketing/getBatchSendEmailTaskStatusHandler.go new file mode 100644 index 0000000..8e5ad87 --- /dev/null +++ b/internal/handler/admin/marketing/getBatchSendEmailTaskStatusHandler.go @@ -0,0 +1,26 @@ +package marketing + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/marketing" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Get batch send email task status +func GetBatchSendEmailTaskStatusHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.GetBatchSendEmailTaskStatusRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := marketing.NewGetBatchSendEmailTaskStatusLogic(c.Request.Context(), svcCtx) + resp, err := l.GetBatchSendEmailTaskStatus(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/marketing/getPreSendEmailCountHandler.go b/internal/handler/admin/marketing/getPreSendEmailCountHandler.go new file mode 100644 index 0000000..33e5894 --- /dev/null +++ b/internal/handler/admin/marketing/getPreSendEmailCountHandler.go @@ -0,0 +1,26 @@ +package marketing + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/marketing" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Get pre-send email count +func GetPreSendEmailCountHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.GetPreSendEmailCountRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := marketing.NewGetPreSendEmailCountLogic(c.Request.Context(), svcCtx) + resp, err := l.GetPreSendEmailCount(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/marketing/queryQuotaTaskListHandler.go b/internal/handler/admin/marketing/queryQuotaTaskListHandler.go new file mode 100644 index 0000000..3aaebdc --- /dev/null +++ b/internal/handler/admin/marketing/queryQuotaTaskListHandler.go @@ -0,0 +1,26 @@ +package marketing + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/marketing" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Query quota task list +func QueryQuotaTaskListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.QueryQuotaTaskListRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := marketing.NewQueryQuotaTaskListLogic(c.Request.Context(), svcCtx) + resp, err := l.QueryQuotaTaskList(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/marketing/queryQuotaTaskPreCountHandler.go b/internal/handler/admin/marketing/queryQuotaTaskPreCountHandler.go new file mode 100644 index 0000000..bcf6bd7 --- /dev/null +++ b/internal/handler/admin/marketing/queryQuotaTaskPreCountHandler.go @@ -0,0 +1,26 @@ +package marketing + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/marketing" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Query quota task pre-count +func QueryQuotaTaskPreCountHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.QueryQuotaTaskPreCountRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := marketing.NewQueryQuotaTaskPreCountLogic(c.Request.Context(), svcCtx) + resp, err := l.QueryQuotaTaskPreCount(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/marketing/queryQuotaTaskStatusHandler.go b/internal/handler/admin/marketing/queryQuotaTaskStatusHandler.go new file mode 100644 index 0000000..8d6cf9c --- /dev/null +++ b/internal/handler/admin/marketing/queryQuotaTaskStatusHandler.go @@ -0,0 +1,26 @@ +package marketing + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/marketing" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Query quota task status +func QueryQuotaTaskStatusHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.QueryQuotaTaskStatusRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := marketing.NewQueryQuotaTaskStatusLogic(c.Request.Context(), svcCtx) + resp, err := l.QueryQuotaTaskStatus(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/marketing/stopBatchSendEmailTaskHandler.go b/internal/handler/admin/marketing/stopBatchSendEmailTaskHandler.go new file mode 100644 index 0000000..0129218 --- /dev/null +++ b/internal/handler/admin/marketing/stopBatchSendEmailTaskHandler.go @@ -0,0 +1,26 @@ +package marketing + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/marketing" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// StopBatchSendEmailTaskHandler Stop a batch send email task +func StopBatchSendEmailTaskHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.StopBatchSendEmailTaskRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := marketing.NewStopBatchSendEmailTaskLogic(c.Request.Context(), svcCtx) + err := l.StopBatchSendEmailTask(&req) + result.HttpResult(c, nil, err) + } +} diff --git a/internal/handler/admin/order/createOrderHandler.go b/internal/handler/admin/order/createOrderHandler.go index f48ba16..346dc99 100644 --- a/internal/handler/admin/order/createOrderHandler.go +++ b/internal/handler/admin/order/createOrderHandler.go @@ -2,10 +2,10 @@ package order import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/order" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Create order diff --git a/internal/handler/admin/order/getOrderListHandler.go b/internal/handler/admin/order/getOrderListHandler.go index 9e732bb..01adc3c 100644 --- a/internal/handler/admin/order/getOrderListHandler.go +++ b/internal/handler/admin/order/getOrderListHandler.go @@ -2,10 +2,10 @@ package order import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/order" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get order list diff --git a/internal/handler/admin/order/updateOrderStatusHandler.go b/internal/handler/admin/order/updateOrderStatusHandler.go index 51a5c19..21a8596 100644 --- a/internal/handler/admin/order/updateOrderStatusHandler.go +++ b/internal/handler/admin/order/updateOrderStatusHandler.go @@ -2,10 +2,10 @@ package order import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/order" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update order status diff --git a/internal/handler/admin/payment/createPaymentMethodHandler.go b/internal/handler/admin/payment/createPaymentMethodHandler.go index 69961a7..f2004a6 100644 --- a/internal/handler/admin/payment/createPaymentMethodHandler.go +++ b/internal/handler/admin/payment/createPaymentMethodHandler.go @@ -2,10 +2,10 @@ package payment import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/payment" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/payment" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Create Payment Method diff --git a/internal/handler/admin/payment/deletePaymentMethodHandler.go b/internal/handler/admin/payment/deletePaymentMethodHandler.go index b3d4365..4bc5af9 100644 --- a/internal/handler/admin/payment/deletePaymentMethodHandler.go +++ b/internal/handler/admin/payment/deletePaymentMethodHandler.go @@ -2,10 +2,10 @@ package payment import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/payment" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/payment" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Delete Payment Method diff --git a/internal/handler/admin/payment/getPaymentMethodListHandler.go b/internal/handler/admin/payment/getPaymentMethodListHandler.go index c968f53..c5b2e22 100644 --- a/internal/handler/admin/payment/getPaymentMethodListHandler.go +++ b/internal/handler/admin/payment/getPaymentMethodListHandler.go @@ -2,10 +2,10 @@ package payment import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/payment" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/payment" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // GetPaymentMethodListHandler Get Payment Method List diff --git a/internal/handler/admin/payment/getPaymentPlatformHandler.go b/internal/handler/admin/payment/getPaymentPlatformHandler.go index 7e8d92e..ffc4b00 100644 --- a/internal/handler/admin/payment/getPaymentPlatformHandler.go +++ b/internal/handler/admin/payment/getPaymentPlatformHandler.go @@ -2,9 +2,9 @@ package payment import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/payment" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/payment" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get supported payment platform diff --git a/internal/handler/admin/payment/updatePaymentMethodHandler.go b/internal/handler/admin/payment/updatePaymentMethodHandler.go index 10b7802..3c30d57 100644 --- a/internal/handler/admin/payment/updatePaymentMethodHandler.go +++ b/internal/handler/admin/payment/updatePaymentMethodHandler.go @@ -2,10 +2,10 @@ package payment import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/payment" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/payment" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update Payment Method diff --git a/internal/handler/admin/server/batchDeleteNodeGroupHandler.go b/internal/handler/admin/server/batchDeleteNodeGroupHandler.go deleted file mode 100644 index caa6411..0000000 --- a/internal/handler/admin/server/batchDeleteNodeGroupHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package server - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Batch delete node group -func BatchDeleteNodeGroupHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.BatchDeleteNodeGroupRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := server.NewBatchDeleteNodeGroupLogic(c.Request.Context(), svcCtx) - err := l.BatchDeleteNodeGroup(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/admin/server/batchDeleteNodeHandler.go b/internal/handler/admin/server/batchDeleteNodeHandler.go deleted file mode 100644 index 221fe86..0000000 --- a/internal/handler/admin/server/batchDeleteNodeHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package server - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Batch delete node -func BatchDeleteNodeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.BatchDeleteNodeRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := server.NewBatchDeleteNodeLogic(c.Request.Context(), svcCtx) - err := l.BatchDeleteNode(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/admin/server/createNodeGroupHandler.go b/internal/handler/admin/server/createNodeGroupHandler.go deleted file mode 100644 index 1523833..0000000 --- a/internal/handler/admin/server/createNodeGroupHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package server - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Create node group -func CreateNodeGroupHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.CreateNodeGroupRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := server.NewCreateNodeGroupLogic(c.Request.Context(), svcCtx) - err := l.CreateNodeGroup(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/admin/server/createNodeHandler.go b/internal/handler/admin/server/createNodeHandler.go index d4b8a98..e872b09 100644 --- a/internal/handler/admin/server/createNodeHandler.go +++ b/internal/handler/admin/server/createNodeHandler.go @@ -2,13 +2,13 @@ package server import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) -// Create node +// Create Node func CreateNodeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { return func(c *gin.Context) { var req types.CreateNodeRequest diff --git a/internal/handler/admin/server/createRuleGroupHandler.go b/internal/handler/admin/server/createRuleGroupHandler.go deleted file mode 100644 index 21c02c4..0000000 --- a/internal/handler/admin/server/createRuleGroupHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package server - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Create rule group -func CreateRuleGroupHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.CreateRuleGroupRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := server.NewCreateRuleGroupLogic(c.Request.Context(), svcCtx) - err := l.CreateRuleGroup(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/admin/server/createServerHandler.go b/internal/handler/admin/server/createServerHandler.go new file mode 100644 index 0000000..2068122 --- /dev/null +++ b/internal/handler/admin/server/createServerHandler.go @@ -0,0 +1,26 @@ +package server + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// CreateServerHandler Create Server +func CreateServerHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.CreateServerRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := server.NewCreateServerLogic(c.Request.Context(), svcCtx) + err := l.CreateServer(&req) + result.HttpResult(c, nil, err) + } +} diff --git a/internal/handler/admin/server/deleteNodeGroupHandler.go b/internal/handler/admin/server/deleteNodeGroupHandler.go deleted file mode 100644 index 492898f..0000000 --- a/internal/handler/admin/server/deleteNodeGroupHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package server - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Delete node group -func DeleteNodeGroupHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.DeleteNodeGroupRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := server.NewDeleteNodeGroupLogic(c.Request.Context(), svcCtx) - err := l.DeleteNodeGroup(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/admin/server/deleteNodeHandler.go b/internal/handler/admin/server/deleteNodeHandler.go index 62f4a60..37ac80b 100644 --- a/internal/handler/admin/server/deleteNodeHandler.go +++ b/internal/handler/admin/server/deleteNodeHandler.go @@ -2,13 +2,13 @@ package server import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) -// Delete node +// Delete Node func DeleteNodeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { return func(c *gin.Context) { var req types.DeleteNodeRequest diff --git a/internal/handler/admin/server/deleteRuleGroupHandler.go b/internal/handler/admin/server/deleteRuleGroupHandler.go deleted file mode 100644 index 91b830e..0000000 --- a/internal/handler/admin/server/deleteRuleGroupHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package server - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Delete rule group -func DeleteRuleGroupHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.DeleteRuleGroupRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := server.NewDeleteRuleGroupLogic(c.Request.Context(), svcCtx) - err := l.DeleteRuleGroup(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/admin/server/deleteServerHandler.go b/internal/handler/admin/server/deleteServerHandler.go new file mode 100644 index 0000000..677fb17 --- /dev/null +++ b/internal/handler/admin/server/deleteServerHandler.go @@ -0,0 +1,26 @@ +package server + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Delete Server +func DeleteServerHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.DeleteServerRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := server.NewDeleteServerLogic(c.Request.Context(), svcCtx) + err := l.DeleteServer(&req) + result.HttpResult(c, nil, err) + } +} diff --git a/internal/handler/admin/server/filterNodeListHandler.go b/internal/handler/admin/server/filterNodeListHandler.go new file mode 100644 index 0000000..5e154ca --- /dev/null +++ b/internal/handler/admin/server/filterNodeListHandler.go @@ -0,0 +1,26 @@ +package server + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Filter Node List +func FilterNodeListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.FilterNodeListRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := server.NewFilterNodeListLogic(c.Request.Context(), svcCtx) + resp, err := l.FilterNodeList(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/server/filterServerListHandler.go b/internal/handler/admin/server/filterServerListHandler.go new file mode 100644 index 0000000..9e6cb7b --- /dev/null +++ b/internal/handler/admin/server/filterServerListHandler.go @@ -0,0 +1,26 @@ +package server + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// FilterServerListHandler Filter Server List +func FilterServerListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.FilterServerListRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := server.NewFilterServerListLogic(c.Request.Context(), svcCtx) + resp, err := l.FilterServerList(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/server/getNodeDetailHandler.go b/internal/handler/admin/server/getNodeDetailHandler.go deleted file mode 100644 index 8485a73..0000000 --- a/internal/handler/admin/server/getNodeDetailHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package server - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get node detail -func GetNodeDetailHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.GetDetailRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := server.NewGetNodeDetailLogic(c.Request.Context(), svcCtx) - resp, err := l.GetNodeDetail(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/admin/server/getNodeGroupListHandler.go b/internal/handler/admin/server/getNodeGroupListHandler.go deleted file mode 100644 index 99bdf3a..0000000 --- a/internal/handler/admin/server/getNodeGroupListHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package server - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get node group list -func GetNodeGroupListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := server.NewGetNodeGroupListLogic(c.Request.Context(), svcCtx) - resp, err := l.GetNodeGroupList() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/admin/server/getNodeListHandler.go b/internal/handler/admin/server/getNodeListHandler.go deleted file mode 100644 index 2c47f74..0000000 --- a/internal/handler/admin/server/getNodeListHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package server - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get node list -func GetNodeListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.GetNodeServerListRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := server.NewGetNodeListLogic(c.Request.Context(), svcCtx) - resp, err := l.GetNodeList(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/admin/server/getNodeTagListHandler.go b/internal/handler/admin/server/getNodeTagListHandler.go deleted file mode 100644 index 700d79b..0000000 --- a/internal/handler/admin/server/getNodeTagListHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package server - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get node tag list -func GetNodeTagListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := server.NewGetNodeTagListLogic(c.Request.Context(), svcCtx) - resp, err := l.GetNodeTagList() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/admin/server/getRuleGroupListHandler.go b/internal/handler/admin/server/getRuleGroupListHandler.go deleted file mode 100644 index 22b14cf..0000000 --- a/internal/handler/admin/server/getRuleGroupListHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package server - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get rule group list -func GetRuleGroupListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := server.NewGetRuleGroupListLogic(c.Request.Context(), svcCtx) - resp, err := l.GetRuleGroupList() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/admin/server/getServerProtocolsHandler.go b/internal/handler/admin/server/getServerProtocolsHandler.go new file mode 100644 index 0000000..14238ca --- /dev/null +++ b/internal/handler/admin/server/getServerProtocolsHandler.go @@ -0,0 +1,26 @@ +package server + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Get Server Protocols +func GetServerProtocolsHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.GetServerProtocolsRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := server.NewGetServerProtocolsLogic(c.Request.Context(), svcCtx) + resp, err := l.GetServerProtocols(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/server/hasMigrateSeverNodeHandler.go b/internal/handler/admin/server/hasMigrateSeverNodeHandler.go new file mode 100644 index 0000000..6088577 --- /dev/null +++ b/internal/handler/admin/server/hasMigrateSeverNodeHandler.go @@ -0,0 +1,18 @@ +package server + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" +) + +// Check if there is any server or node to migrate +func HasMigrateSeverNodeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + + l := server.NewHasMigrateSeverNodeLogic(c.Request.Context(), svcCtx) + resp, err := l.HasMigrateSeverNode() + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/server/migrateServerNodeHandler.go b/internal/handler/admin/server/migrateServerNodeHandler.go new file mode 100644 index 0000000..8f8c842 --- /dev/null +++ b/internal/handler/admin/server/migrateServerNodeHandler.go @@ -0,0 +1,18 @@ +package server + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" +) + +// Migrate server and node data to new database +func MigrateServerNodeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + + l := server.NewMigrateServerNodeLogic(c.Request.Context(), svcCtx) + resp, err := l.MigrateServerNode() + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/server/nodeSortHandler.go b/internal/handler/admin/server/nodeSortHandler.go deleted file mode 100644 index 84c5184..0000000 --- a/internal/handler/admin/server/nodeSortHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package server - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Node sort -func NodeSortHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.NodeSortRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := server.NewNodeSortLogic(c.Request.Context(), svcCtx) - err := l.NodeSort(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/admin/server/queryNodeTagHandler.go b/internal/handler/admin/server/queryNodeTagHandler.go new file mode 100644 index 0000000..fa963cc --- /dev/null +++ b/internal/handler/admin/server/queryNodeTagHandler.go @@ -0,0 +1,18 @@ +package server + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" +) + +// Query all node tags +func QueryNodeTagHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + + l := server.NewQueryNodeTagLogic(c.Request.Context(), svcCtx) + resp, err := l.QueryNodeTag() + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/server/resetSortWithNodeHandler.go b/internal/handler/admin/server/resetSortWithNodeHandler.go new file mode 100644 index 0000000..4b8b14c --- /dev/null +++ b/internal/handler/admin/server/resetSortWithNodeHandler.go @@ -0,0 +1,26 @@ +package server + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Reset node sort +func ResetSortWithNodeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.ResetSortRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := server.NewResetSortWithNodeLogic(c.Request.Context(), svcCtx) + err := l.ResetSortWithNode(&req) + result.HttpResult(c, nil, err) + } +} diff --git a/internal/handler/admin/server/resetSortWithServerHandler.go b/internal/handler/admin/server/resetSortWithServerHandler.go new file mode 100644 index 0000000..7adbecb --- /dev/null +++ b/internal/handler/admin/server/resetSortWithServerHandler.go @@ -0,0 +1,26 @@ +package server + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Reset server sort +func ResetSortWithServerHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.ResetSortRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := server.NewResetSortWithServerLogic(c.Request.Context(), svcCtx) + err := l.ResetSortWithServer(&req) + result.HttpResult(c, nil, err) + } +} diff --git a/internal/handler/admin/server/toggleNodeStatusHandler.go b/internal/handler/admin/server/toggleNodeStatusHandler.go new file mode 100644 index 0000000..67144ad --- /dev/null +++ b/internal/handler/admin/server/toggleNodeStatusHandler.go @@ -0,0 +1,26 @@ +package server + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Toggle Node Status +func ToggleNodeStatusHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.ToggleNodeStatusRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := server.NewToggleNodeStatusLogic(c.Request.Context(), svcCtx) + err := l.ToggleNodeStatus(&req) + result.HttpResult(c, nil, err) + } +} diff --git a/internal/handler/admin/server/updateNodeGroupHandler.go b/internal/handler/admin/server/updateNodeGroupHandler.go deleted file mode 100644 index 4b4d2de..0000000 --- a/internal/handler/admin/server/updateNodeGroupHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package server - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Update node group -func UpdateNodeGroupHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.UpdateNodeGroupRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := server.NewUpdateNodeGroupLogic(c.Request.Context(), svcCtx) - err := l.UpdateNodeGroup(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/admin/server/updateNodeHandler.go b/internal/handler/admin/server/updateNodeHandler.go index 8ced9af..af19537 100644 --- a/internal/handler/admin/server/updateNodeHandler.go +++ b/internal/handler/admin/server/updateNodeHandler.go @@ -2,13 +2,13 @@ package server import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) -// Update node +// Update Node func UpdateNodeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { return func(c *gin.Context) { var req types.UpdateNodeRequest diff --git a/internal/handler/admin/server/updateRuleGroupHandler.go b/internal/handler/admin/server/updateRuleGroupHandler.go deleted file mode 100644 index e77cfc1..0000000 --- a/internal/handler/admin/server/updateRuleGroupHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package server - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Update rule group -func UpdateRuleGroupHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.UpdateRuleGroupRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := server.NewUpdateRuleGroupLogic(c.Request.Context(), svcCtx) - err := l.UpdateRuleGroup(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/admin/server/updateServerHandler.go b/internal/handler/admin/server/updateServerHandler.go new file mode 100644 index 0000000..24570c6 --- /dev/null +++ b/internal/handler/admin/server/updateServerHandler.go @@ -0,0 +1,26 @@ +package server + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Update Server +func UpdateServerHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.UpdateServerRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := server.NewUpdateServerLogic(c.Request.Context(), svcCtx) + err := l.UpdateServer(&req) + result.HttpResult(c, nil, err) + } +} diff --git a/internal/handler/admin/subscribe/batchDeleteSubscribeGroupHandler.go b/internal/handler/admin/subscribe/batchDeleteSubscribeGroupHandler.go index 1d2a3c9..001396d 100644 --- a/internal/handler/admin/subscribe/batchDeleteSubscribeGroupHandler.go +++ b/internal/handler/admin/subscribe/batchDeleteSubscribeGroupHandler.go @@ -2,10 +2,10 @@ package subscribe import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/subscribe" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Batch delete subscribe group diff --git a/internal/handler/admin/subscribe/batchDeleteSubscribeHandler.go b/internal/handler/admin/subscribe/batchDeleteSubscribeHandler.go index e16bb2c..46af1ef 100644 --- a/internal/handler/admin/subscribe/batchDeleteSubscribeHandler.go +++ b/internal/handler/admin/subscribe/batchDeleteSubscribeHandler.go @@ -2,10 +2,10 @@ package subscribe import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/subscribe" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Batch delete subscribe diff --git a/internal/handler/admin/subscribe/createSubscribeGroupHandler.go b/internal/handler/admin/subscribe/createSubscribeGroupHandler.go index c0dd388..4c0ede9 100644 --- a/internal/handler/admin/subscribe/createSubscribeGroupHandler.go +++ b/internal/handler/admin/subscribe/createSubscribeGroupHandler.go @@ -2,10 +2,10 @@ package subscribe import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/subscribe" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Create subscribe group diff --git a/internal/handler/admin/subscribe/createSubscribeHandler.go b/internal/handler/admin/subscribe/createSubscribeHandler.go index 95c7c83..2409777 100644 --- a/internal/handler/admin/subscribe/createSubscribeHandler.go +++ b/internal/handler/admin/subscribe/createSubscribeHandler.go @@ -2,10 +2,10 @@ package subscribe import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/subscribe" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Create subscribe diff --git a/internal/handler/admin/subscribe/deleteSubscribeGroupHandler.go b/internal/handler/admin/subscribe/deleteSubscribeGroupHandler.go index 54d20df..db8df90 100644 --- a/internal/handler/admin/subscribe/deleteSubscribeGroupHandler.go +++ b/internal/handler/admin/subscribe/deleteSubscribeGroupHandler.go @@ -2,10 +2,10 @@ package subscribe import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/subscribe" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Delete subscribe group diff --git a/internal/handler/admin/subscribe/deleteSubscribeHandler.go b/internal/handler/admin/subscribe/deleteSubscribeHandler.go index b25b1cb..711eae0 100644 --- a/internal/handler/admin/subscribe/deleteSubscribeHandler.go +++ b/internal/handler/admin/subscribe/deleteSubscribeHandler.go @@ -2,10 +2,10 @@ package subscribe import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/subscribe" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Delete subscribe diff --git a/internal/handler/admin/subscribe/getSubscribeDetailsHandler.go b/internal/handler/admin/subscribe/getSubscribeDetailsHandler.go index 216e753..371b149 100644 --- a/internal/handler/admin/subscribe/getSubscribeDetailsHandler.go +++ b/internal/handler/admin/subscribe/getSubscribeDetailsHandler.go @@ -2,10 +2,10 @@ package subscribe import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/subscribe" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get subscribe details diff --git a/internal/handler/admin/subscribe/getSubscribeGroupListHandler.go b/internal/handler/admin/subscribe/getSubscribeGroupListHandler.go index 980dbb4..a629f02 100644 --- a/internal/handler/admin/subscribe/getSubscribeGroupListHandler.go +++ b/internal/handler/admin/subscribe/getSubscribeGroupListHandler.go @@ -2,9 +2,9 @@ package subscribe import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/subscribe" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get subscribe group list diff --git a/internal/handler/admin/subscribe/getSubscribeListHandler.go b/internal/handler/admin/subscribe/getSubscribeListHandler.go index eedf89d..41e4e6d 100644 --- a/internal/handler/admin/subscribe/getSubscribeListHandler.go +++ b/internal/handler/admin/subscribe/getSubscribeListHandler.go @@ -2,10 +2,10 @@ package subscribe import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/subscribe" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get subscribe list diff --git a/internal/handler/admin/subscribe/subscribeSortHandler.go b/internal/handler/admin/subscribe/subscribeSortHandler.go index 8f7c5b6..d195279 100644 --- a/internal/handler/admin/subscribe/subscribeSortHandler.go +++ b/internal/handler/admin/subscribe/subscribeSortHandler.go @@ -2,10 +2,10 @@ package subscribe import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/subscribe" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Subscribe sort diff --git a/internal/handler/admin/subscribe/updateSubscribeGroupHandler.go b/internal/handler/admin/subscribe/updateSubscribeGroupHandler.go index 53d9fd1..dc37c4f 100644 --- a/internal/handler/admin/subscribe/updateSubscribeGroupHandler.go +++ b/internal/handler/admin/subscribe/updateSubscribeGroupHandler.go @@ -2,10 +2,10 @@ package subscribe import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/subscribe" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update subscribe group diff --git a/internal/handler/admin/subscribe/updateSubscribeHandler.go b/internal/handler/admin/subscribe/updateSubscribeHandler.go index c01bcc9..2c54697 100644 --- a/internal/handler/admin/subscribe/updateSubscribeHandler.go +++ b/internal/handler/admin/subscribe/updateSubscribeHandler.go @@ -2,10 +2,10 @@ package subscribe import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/subscribe" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update subscribe diff --git a/internal/handler/admin/system/createApplicationHandler.go b/internal/handler/admin/system/createApplicationHandler.go deleted file mode 100644 index f2745f1..0000000 --- a/internal/handler/admin/system/createApplicationHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package system - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Create application -func CreateApplicationHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.CreateApplicationRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := system.NewCreateApplicationLogic(c.Request.Context(), svcCtx) - err := l.CreateApplication(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/admin/system/createApplicationVersionHandler.go b/internal/handler/admin/system/createApplicationVersionHandler.go deleted file mode 100644 index bbae577..0000000 --- a/internal/handler/admin/system/createApplicationVersionHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package system - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Create application version -func CreateApplicationVersionHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.CreateApplicationVersionRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := system.NewCreateApplicationVersionLogic(c.Request.Context(), svcCtx) - err := l.CreateApplicationVersion(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/admin/system/deleteApplicationHandler.go b/internal/handler/admin/system/deleteApplicationHandler.go deleted file mode 100644 index 3de1aa4..0000000 --- a/internal/handler/admin/system/deleteApplicationHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package system - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Delete application -func DeleteApplicationHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.DeleteApplicationRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := system.NewDeleteApplicationLogic(c.Request.Context(), svcCtx) - err := l.DeleteApplication(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/admin/system/deleteApplicationVersionHandler.go b/internal/handler/admin/system/deleteApplicationVersionHandler.go deleted file mode 100644 index 7f9516f..0000000 --- a/internal/handler/admin/system/deleteApplicationVersionHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package system - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Delete application -func DeleteApplicationVersionHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.DeleteApplicationVersionRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := system.NewDeleteApplicationVersionLogic(c.Request.Context(), svcCtx) - err := l.DeleteApplicationVersion(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/admin/system/getApplicationConfigHandler.go b/internal/handler/admin/system/getApplicationConfigHandler.go deleted file mode 100644 index 0f8ddab..0000000 --- a/internal/handler/admin/system/getApplicationConfigHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package system - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// get application config -func GetApplicationConfigHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := system.NewGetApplicationConfigLogic(c.Request.Context(), svcCtx) - resp, err := l.GetApplicationConfig() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/admin/system/getApplicationHandler.go b/internal/handler/admin/system/getApplicationHandler.go deleted file mode 100644 index a0eefbd..0000000 --- a/internal/handler/admin/system/getApplicationHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package system - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get application -func GetApplicationHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := system.NewGetApplicationLogic(c.Request.Context(), svcCtx) - resp, err := l.GetApplication() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/admin/system/getCurrencyConfigHandler.go b/internal/handler/admin/system/getCurrencyConfigHandler.go index a172227..3a39888 100644 --- a/internal/handler/admin/system/getCurrencyConfigHandler.go +++ b/internal/handler/admin/system/getCurrencyConfigHandler.go @@ -2,9 +2,9 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get Currency Config diff --git a/internal/handler/admin/system/getInviteConfigHandler.go b/internal/handler/admin/system/getInviteConfigHandler.go index f586852..ed777c6 100644 --- a/internal/handler/admin/system/getInviteConfigHandler.go +++ b/internal/handler/admin/system/getInviteConfigHandler.go @@ -2,9 +2,9 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get invite config diff --git a/internal/handler/admin/system/getNodeConfigHandler.go b/internal/handler/admin/system/getNodeConfigHandler.go index 0a0aba6..360b10a 100644 --- a/internal/handler/admin/system/getNodeConfigHandler.go +++ b/internal/handler/admin/system/getNodeConfigHandler.go @@ -2,9 +2,9 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get node config diff --git a/internal/handler/admin/system/getNodeMultiplierHandler.go b/internal/handler/admin/system/getNodeMultiplierHandler.go index 5268a6a..ca845a6 100644 --- a/internal/handler/admin/system/getNodeMultiplierHandler.go +++ b/internal/handler/admin/system/getNodeMultiplierHandler.go @@ -2,9 +2,9 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get Node Multiplier diff --git a/internal/handler/admin/system/getPrivacyPolicyConfigHandler.go b/internal/handler/admin/system/getPrivacyPolicyConfigHandler.go index 3f86383..62ed8d2 100644 --- a/internal/handler/admin/system/getPrivacyPolicyConfigHandler.go +++ b/internal/handler/admin/system/getPrivacyPolicyConfigHandler.go @@ -2,9 +2,9 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // get Privacy Policy Config diff --git a/internal/handler/admin/system/getRegisterConfigHandler.go b/internal/handler/admin/system/getRegisterConfigHandler.go index b8b9687..9eab273 100644 --- a/internal/handler/admin/system/getRegisterConfigHandler.go +++ b/internal/handler/admin/system/getRegisterConfigHandler.go @@ -2,9 +2,9 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get register config diff --git a/internal/handler/admin/system/getSiteConfigHandler.go b/internal/handler/admin/system/getSiteConfigHandler.go index 109a1ee..ea9a376 100644 --- a/internal/handler/admin/system/getSiteConfigHandler.go +++ b/internal/handler/admin/system/getSiteConfigHandler.go @@ -2,9 +2,9 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get site config diff --git a/internal/handler/admin/system/getSubscribeConfigHandler.go b/internal/handler/admin/system/getSubscribeConfigHandler.go index 0b5fb19..0cca035 100644 --- a/internal/handler/admin/system/getSubscribeConfigHandler.go +++ b/internal/handler/admin/system/getSubscribeConfigHandler.go @@ -2,9 +2,9 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get subscribe config diff --git a/internal/handler/admin/system/getSubscribeTypeHandler.go b/internal/handler/admin/system/getSubscribeTypeHandler.go deleted file mode 100644 index f017cf7..0000000 --- a/internal/handler/admin/system/getSubscribeTypeHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package system - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get subscribe type -func GetSubscribeTypeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := system.NewGetSubscribeTypeLogic(c.Request.Context(), svcCtx) - resp, err := l.GetSubscribeType() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/admin/system/getTosConfigHandler.go b/internal/handler/admin/system/getTosConfigHandler.go index 06c6cc0..2215991 100644 --- a/internal/handler/admin/system/getTosConfigHandler.go +++ b/internal/handler/admin/system/getTosConfigHandler.go @@ -2,9 +2,9 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get Team of Service Config diff --git a/internal/handler/admin/system/getVerifyCodeConfigHandler.go b/internal/handler/admin/system/getVerifyCodeConfigHandler.go index 4a23c1b..2936d61 100644 --- a/internal/handler/admin/system/getVerifyCodeConfigHandler.go +++ b/internal/handler/admin/system/getVerifyCodeConfigHandler.go @@ -2,9 +2,9 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get Verify Code Config diff --git a/internal/handler/admin/system/getVerifyConfigHandler.go b/internal/handler/admin/system/getVerifyConfigHandler.go index eb928dd..2a9a03b 100644 --- a/internal/handler/admin/system/getVerifyConfigHandler.go +++ b/internal/handler/admin/system/getVerifyConfigHandler.go @@ -2,9 +2,9 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get verify config diff --git a/internal/handler/admin/system/preViewNodeMultiplierHandler.go b/internal/handler/admin/system/preViewNodeMultiplierHandler.go new file mode 100644 index 0000000..8ef2089 --- /dev/null +++ b/internal/handler/admin/system/preViewNodeMultiplierHandler.go @@ -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" +) + +// PreView Node Multiplier +func PreViewNodeMultiplierHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + + l := system.NewPreViewNodeMultiplierLogic(c.Request.Context(), svcCtx) + resp, err := l.PreViewNodeMultiplier() + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/system/setNodeMultiplierHandler.go b/internal/handler/admin/system/setNodeMultiplierHandler.go index 2b01f46..7abc922 100644 --- a/internal/handler/admin/system/setNodeMultiplierHandler.go +++ b/internal/handler/admin/system/setNodeMultiplierHandler.go @@ -2,10 +2,10 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Set Node Multiplier diff --git a/internal/handler/admin/system/settingTelegramBotHandler.go b/internal/handler/admin/system/settingTelegramBotHandler.go index aac5cc4..c6e9087 100644 --- a/internal/handler/admin/system/settingTelegramBotHandler.go +++ b/internal/handler/admin/system/settingTelegramBotHandler.go @@ -2,9 +2,9 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // setting telegram bot diff --git a/internal/handler/admin/system/updateApplicationConfigHandler.go b/internal/handler/admin/system/updateApplicationConfigHandler.go deleted file mode 100644 index 84a296e..0000000 --- a/internal/handler/admin/system/updateApplicationConfigHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package system - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// update application config -func UpdateApplicationConfigHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.ApplicationConfig - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := system.NewUpdateApplicationConfigLogic(c.Request.Context(), svcCtx) - err := l.UpdateApplicationConfig(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/admin/system/updateApplicationHandler.go b/internal/handler/admin/system/updateApplicationHandler.go deleted file mode 100644 index f7a0de0..0000000 --- a/internal/handler/admin/system/updateApplicationHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package system - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Update application -func UpdateApplicationHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.UpdateApplicationRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := system.NewUpdateApplicationLogic(c.Request.Context(), svcCtx) - err := l.UpdateApplication(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/admin/system/updateApplicationVersionHandler.go b/internal/handler/admin/system/updateApplicationVersionHandler.go deleted file mode 100644 index fffe6e3..0000000 --- a/internal/handler/admin/system/updateApplicationVersionHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package system - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Update application version -func UpdateApplicationVersionHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.UpdateApplicationVersionRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := system.NewUpdateApplicationVersionLogic(c.Request.Context(), svcCtx) - err := l.UpdateApplicationVersion(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/admin/system/updateCurrencyConfigHandler.go b/internal/handler/admin/system/updateCurrencyConfigHandler.go index 1bcae53..3c5467e 100644 --- a/internal/handler/admin/system/updateCurrencyConfigHandler.go +++ b/internal/handler/admin/system/updateCurrencyConfigHandler.go @@ -2,10 +2,10 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update Currency Config diff --git a/internal/handler/admin/system/updateInviteConfigHandler.go b/internal/handler/admin/system/updateInviteConfigHandler.go index b9e6bbb..2151edf 100644 --- a/internal/handler/admin/system/updateInviteConfigHandler.go +++ b/internal/handler/admin/system/updateInviteConfigHandler.go @@ -2,10 +2,10 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update invite config diff --git a/internal/handler/admin/system/updateNodeConfigHandler.go b/internal/handler/admin/system/updateNodeConfigHandler.go index ceb95de..01e7bbf 100644 --- a/internal/handler/admin/system/updateNodeConfigHandler.go +++ b/internal/handler/admin/system/updateNodeConfigHandler.go @@ -2,10 +2,10 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update node config diff --git a/internal/handler/admin/system/updatePrivacyPolicyConfigHandler.go b/internal/handler/admin/system/updatePrivacyPolicyConfigHandler.go index ca5168b..fc8bb33 100644 --- a/internal/handler/admin/system/updatePrivacyPolicyConfigHandler.go +++ b/internal/handler/admin/system/updatePrivacyPolicyConfigHandler.go @@ -2,10 +2,10 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update Privacy Policy Config diff --git a/internal/handler/admin/system/updateRegisterConfigHandler.go b/internal/handler/admin/system/updateRegisterConfigHandler.go index 507b7b9..fd43850 100644 --- a/internal/handler/admin/system/updateRegisterConfigHandler.go +++ b/internal/handler/admin/system/updateRegisterConfigHandler.go @@ -2,10 +2,10 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update register config diff --git a/internal/handler/admin/system/updateSiteConfigHandler.go b/internal/handler/admin/system/updateSiteConfigHandler.go index 8ecb211..6692ecf 100644 --- a/internal/handler/admin/system/updateSiteConfigHandler.go +++ b/internal/handler/admin/system/updateSiteConfigHandler.go @@ -2,10 +2,10 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update site config diff --git a/internal/handler/admin/system/updateSubscribeConfigHandler.go b/internal/handler/admin/system/updateSubscribeConfigHandler.go index 185302b..3316cc6 100644 --- a/internal/handler/admin/system/updateSubscribeConfigHandler.go +++ b/internal/handler/admin/system/updateSubscribeConfigHandler.go @@ -2,10 +2,10 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update subscribe config diff --git a/internal/handler/admin/system/updateTosConfigHandler.go b/internal/handler/admin/system/updateTosConfigHandler.go index 16c71e2..ee77fba 100644 --- a/internal/handler/admin/system/updateTosConfigHandler.go +++ b/internal/handler/admin/system/updateTosConfigHandler.go @@ -2,10 +2,10 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update Team of Service Config diff --git a/internal/handler/admin/system/updateVerifyCodeConfigHandler.go b/internal/handler/admin/system/updateVerifyCodeConfigHandler.go index 3c27154..04e2902 100644 --- a/internal/handler/admin/system/updateVerifyCodeConfigHandler.go +++ b/internal/handler/admin/system/updateVerifyCodeConfigHandler.go @@ -2,10 +2,10 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update Verify Code Config diff --git a/internal/handler/admin/system/updateVerifyConfigHandler.go b/internal/handler/admin/system/updateVerifyConfigHandler.go index 483985c..730425d 100644 --- a/internal/handler/admin/system/updateVerifyConfigHandler.go +++ b/internal/handler/admin/system/updateVerifyConfigHandler.go @@ -2,10 +2,10 @@ package system import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update verify config diff --git a/internal/handler/admin/ticket/createTicketFollowHandler.go b/internal/handler/admin/ticket/createTicketFollowHandler.go index 34b6b2d..6ede0fc 100644 --- a/internal/handler/admin/ticket/createTicketFollowHandler.go +++ b/internal/handler/admin/ticket/createTicketFollowHandler.go @@ -2,10 +2,10 @@ package ticket import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/ticket" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/ticket" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Create ticket follow diff --git a/internal/handler/admin/ticket/getTicketHandler.go b/internal/handler/admin/ticket/getTicketHandler.go index 5058a7f..b8e8ccb 100644 --- a/internal/handler/admin/ticket/getTicketHandler.go +++ b/internal/handler/admin/ticket/getTicketHandler.go @@ -2,10 +2,10 @@ package ticket import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/ticket" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/ticket" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get ticket detail diff --git a/internal/handler/admin/ticket/getTicketListHandler.go b/internal/handler/admin/ticket/getTicketListHandler.go index bfca05c..2fe7264 100644 --- a/internal/handler/admin/ticket/getTicketListHandler.go +++ b/internal/handler/admin/ticket/getTicketListHandler.go @@ -2,10 +2,10 @@ package ticket import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/ticket" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/ticket" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get ticket list diff --git a/internal/handler/admin/ticket/updateTicketStatusHandler.go b/internal/handler/admin/ticket/updateTicketStatusHandler.go index f710b98..30ca534 100644 --- a/internal/handler/admin/ticket/updateTicketStatusHandler.go +++ b/internal/handler/admin/ticket/updateTicketStatusHandler.go @@ -2,10 +2,10 @@ package ticket import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/ticket" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/ticket" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update ticket status diff --git a/internal/handler/admin/tool/getSystemLogHandler.go b/internal/handler/admin/tool/getSystemLogHandler.go index 80dd1d3..fe34bca 100644 --- a/internal/handler/admin/tool/getSystemLogHandler.go +++ b/internal/handler/admin/tool/getSystemLogHandler.go @@ -2,9 +2,9 @@ package tool import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/tool" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/tool" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get System Log diff --git a/internal/handler/admin/tool/getVersionHandler.go b/internal/handler/admin/tool/getVersionHandler.go new file mode 100644 index 0000000..d1c0bce --- /dev/null +++ b/internal/handler/admin/tool/getVersionHandler.go @@ -0,0 +1,18 @@ +package tool + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/tool" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" +) + +// GetVersionHandler Get Version +func GetVersionHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + + l := tool.NewGetVersionLogic(c.Request.Context(), svcCtx) + resp, err := l.GetVersion() + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/tool/restartSystemHandler.go b/internal/handler/admin/tool/restartSystemHandler.go index 5d391b0..e77183c 100644 --- a/internal/handler/admin/tool/restartSystemHandler.go +++ b/internal/handler/admin/tool/restartSystemHandler.go @@ -2,9 +2,9 @@ package tool import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/tool" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/tool" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Restart System diff --git a/internal/handler/admin/user/batchDeleteUserHandler.go b/internal/handler/admin/user/batchDeleteUserHandler.go index d0f1c65..e58bdd5 100644 --- a/internal/handler/admin/user/batchDeleteUserHandler.go +++ b/internal/handler/admin/user/batchDeleteUserHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Batch delete user diff --git a/internal/handler/admin/user/createUserAuthMethodHandler.go b/internal/handler/admin/user/createUserAuthMethodHandler.go index de69a03..41e4467 100644 --- a/internal/handler/admin/user/createUserAuthMethodHandler.go +++ b/internal/handler/admin/user/createUserAuthMethodHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Create user auth method diff --git a/internal/handler/admin/user/createUserHandler.go b/internal/handler/admin/user/createUserHandler.go index 4de5e76..5de76e6 100644 --- a/internal/handler/admin/user/createUserHandler.go +++ b/internal/handler/admin/user/createUserHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Create user diff --git a/internal/handler/admin/user/createUserSubscribeHandler.go b/internal/handler/admin/user/createUserSubscribeHandler.go index 556c75c..3f1a34b 100644 --- a/internal/handler/admin/user/createUserSubscribeHandler.go +++ b/internal/handler/admin/user/createUserSubscribeHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Create user subcribe diff --git a/internal/handler/admin/user/currentUserHandler.go b/internal/handler/admin/user/currentUserHandler.go index 3851558..9f65159 100644 --- a/internal/handler/admin/user/currentUserHandler.go +++ b/internal/handler/admin/user/currentUserHandler.go @@ -2,9 +2,9 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Current user diff --git a/internal/handler/admin/user/deleteUserAuthMethodHandler.go b/internal/handler/admin/user/deleteUserAuthMethodHandler.go index f9cde21..80a5fcc 100644 --- a/internal/handler/admin/user/deleteUserAuthMethodHandler.go +++ b/internal/handler/admin/user/deleteUserAuthMethodHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Delete user auth method diff --git a/internal/handler/admin/user/deleteUserDeviceHandler.go b/internal/handler/admin/user/deleteUserDeviceHandler.go index 6269cbe..3dd4b13 100644 --- a/internal/handler/admin/user/deleteUserDeviceHandler.go +++ b/internal/handler/admin/user/deleteUserDeviceHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Delete user device diff --git a/internal/handler/admin/user/deleteUserHandler.go b/internal/handler/admin/user/deleteUserHandler.go index 7e077b1..70a9410 100644 --- a/internal/handler/admin/user/deleteUserHandler.go +++ b/internal/handler/admin/user/deleteUserHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Delete user diff --git a/internal/handler/admin/user/deleteUserSubscribeHandler.go b/internal/handler/admin/user/deleteUserSubscribeHandler.go index 9ff53da..febcea0 100644 --- a/internal/handler/admin/user/deleteUserSubscribeHandler.go +++ b/internal/handler/admin/user/deleteUserSubscribeHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Delete user subcribe diff --git a/internal/handler/admin/user/getUserAuthMethodHandler.go b/internal/handler/admin/user/getUserAuthMethodHandler.go index 9e5c60c..f93494b 100644 --- a/internal/handler/admin/user/getUserAuthMethodHandler.go +++ b/internal/handler/admin/user/getUserAuthMethodHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get user auth method diff --git a/internal/handler/admin/user/getUserDetailHandler.go b/internal/handler/admin/user/getUserDetailHandler.go index 30a8f50..aadd63c 100644 --- a/internal/handler/admin/user/getUserDetailHandler.go +++ b/internal/handler/admin/user/getUserDetailHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get user detail diff --git a/internal/handler/admin/user/getUserListHandler.go b/internal/handler/admin/user/getUserListHandler.go index 6af432f..54eff40 100644 --- a/internal/handler/admin/user/getUserListHandler.go +++ b/internal/handler/admin/user/getUserListHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get user list diff --git a/internal/handler/admin/user/getUserLoginLogsHandler.go b/internal/handler/admin/user/getUserLoginLogsHandler.go index 99646c7..fa2ee60 100644 --- a/internal/handler/admin/user/getUserLoginLogsHandler.go +++ b/internal/handler/admin/user/getUserLoginLogsHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get user login logs diff --git a/internal/handler/admin/user/getUserSubscribeByIdHandler.go b/internal/handler/admin/user/getUserSubscribeByIdHandler.go index 49df56a..e591e59 100644 --- a/internal/handler/admin/user/getUserSubscribeByIdHandler.go +++ b/internal/handler/admin/user/getUserSubscribeByIdHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get user subcribe by id diff --git a/internal/handler/admin/user/getUserSubscribeDevicesHandler.go b/internal/handler/admin/user/getUserSubscribeDevicesHandler.go index cc342fb..4f8ecdf 100644 --- a/internal/handler/admin/user/getUserSubscribeDevicesHandler.go +++ b/internal/handler/admin/user/getUserSubscribeDevicesHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get user subcribe devices diff --git a/internal/handler/admin/user/getUserSubscribeHandler.go b/internal/handler/admin/user/getUserSubscribeHandler.go index 1bf0125..57c151f 100644 --- a/internal/handler/admin/user/getUserSubscribeHandler.go +++ b/internal/handler/admin/user/getUserSubscribeHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get user subcribe diff --git a/internal/handler/admin/user/getUserSubscribeLogsHandler.go b/internal/handler/admin/user/getUserSubscribeLogsHandler.go index a9a4263..7b8d362 100644 --- a/internal/handler/admin/user/getUserSubscribeLogsHandler.go +++ b/internal/handler/admin/user/getUserSubscribeLogsHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get user subcribe logs diff --git a/internal/handler/admin/user/getUserSubscribeResetTrafficLogsHandler.go b/internal/handler/admin/user/getUserSubscribeResetTrafficLogsHandler.go new file mode 100644 index 0000000..0f7525d --- /dev/null +++ b/internal/handler/admin/user/getUserSubscribeResetTrafficLogsHandler.go @@ -0,0 +1,26 @@ +package user + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Get user subcribe reset traffic logs +func GetUserSubscribeResetTrafficLogsHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.GetUserSubscribeResetTrafficLogsRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := user.NewGetUserSubscribeResetTrafficLogsLogic(c.Request.Context(), svcCtx) + resp, err := l.GetUserSubscribeResetTrafficLogs(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/user/getUserSubscribeTrafficLogsHandler.go b/internal/handler/admin/user/getUserSubscribeTrafficLogsHandler.go index f6cf5fb..ce42b79 100644 --- a/internal/handler/admin/user/getUserSubscribeTrafficLogsHandler.go +++ b/internal/handler/admin/user/getUserSubscribeTrafficLogsHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get user subcribe traffic logs diff --git a/internal/handler/admin/user/kickOfflineByUserDeviceHandler.go b/internal/handler/admin/user/kickOfflineByUserDeviceHandler.go index ab9b4f6..d4b99c8 100644 --- a/internal/handler/admin/user/kickOfflineByUserDeviceHandler.go +++ b/internal/handler/admin/user/kickOfflineByUserDeviceHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // kick offline user device diff --git a/internal/handler/admin/user/updateUserAuthMethodHandler.go b/internal/handler/admin/user/updateUserAuthMethodHandler.go index 1f289c8..354fcf2 100644 --- a/internal/handler/admin/user/updateUserAuthMethodHandler.go +++ b/internal/handler/admin/user/updateUserAuthMethodHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update user auth method diff --git a/internal/handler/admin/user/updateUserBasicInfoHandler.go b/internal/handler/admin/user/updateUserBasicInfoHandler.go index 4187bfc..7a1fc78 100644 --- a/internal/handler/admin/user/updateUserBasicInfoHandler.go +++ b/internal/handler/admin/user/updateUserBasicInfoHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update user basic info diff --git a/internal/handler/admin/user/updateUserDeviceHandler.go b/internal/handler/admin/user/updateUserDeviceHandler.go index ae1b813..5b4e178 100644 --- a/internal/handler/admin/user/updateUserDeviceHandler.go +++ b/internal/handler/admin/user/updateUserDeviceHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // User device diff --git a/internal/handler/admin/user/updateUserNotifySettingHandler.go b/internal/handler/admin/user/updateUserNotifySettingHandler.go index 3644c5b..a4fc785 100644 --- a/internal/handler/admin/user/updateUserNotifySettingHandler.go +++ b/internal/handler/admin/user/updateUserNotifySettingHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update user notify setting diff --git a/internal/handler/admin/user/updateUserSubscribeHandler.go b/internal/handler/admin/user/updateUserSubscribeHandler.go index c1bca31..3e111d3 100644 --- a/internal/handler/admin/user/updateUserSubscribeHandler.go +++ b/internal/handler/admin/user/updateUserSubscribeHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/admin/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update user subcribe diff --git a/internal/handler/app/announcement/queryannouncementhandler.go b/internal/handler/app/announcement/queryannouncementhandler.go deleted file mode 100644 index 7eec557..0000000 --- a/internal/handler/app/announcement/queryannouncementhandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package announcement - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/announcement" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Query announcement -func QueryAnnouncementHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.QueryAnnouncementRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := announcement.NewQueryAnnouncementLogic(c.Request.Context(), svcCtx) - resp, err := l.QueryAnnouncement(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/auth/checkHandler.go b/internal/handler/app/auth/checkHandler.go deleted file mode 100644 index 6265f38..0000000 --- a/internal/handler/app/auth/checkHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package auth - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Check Account -func CheckHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.AppAuthCheckRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := auth.NewCheckLogic(c, svcCtx) - resp, err := l.Check(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/auth/getAppConfigHandler.go b/internal/handler/app/auth/getAppConfigHandler.go deleted file mode 100644 index 83ea8c5..0000000 --- a/internal/handler/app/auth/getAppConfigHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package auth - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// GetAppConfig -func GetAppConfigHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.AppConfigRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := auth.NewGetAppConfigLogic(c, svcCtx) - resp, err := l.GetAppConfig(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/auth/loginHandler.go b/internal/handler/app/auth/loginHandler.go deleted file mode 100644 index b6cbe1a..0000000 --- a/internal/handler/app/auth/loginHandler.go +++ /dev/null @@ -1,42 +0,0 @@ -package auth - -import ( - "time" - - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" - "github.com/perfect-panel/ppanel-server/pkg/turnstile" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -// Login -func LoginHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.AppAuthRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - if svcCtx.Config.Verify.LoginVerify { - verifyTurns := turnstile.New(turnstile.Config{ - Secret: svcCtx.Config.Verify.TurnstileSecret, - Timeout: 3 * time.Second, - }) - if verify, err := verifyTurns.Verify(c, req.CfToken, c.ClientIP()); err != nil || !verify { - err = errors.Wrapf(xerr.NewErrCode(xerr.TooManyRequests), "error: %v, verify: %v", err, verify) - result.HttpResult(c, nil, err) - return - } - } - l := auth.NewLoginLogic(c, svcCtx) - resp, err := l.Login(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/auth/registerHandler.go b/internal/handler/app/auth/registerHandler.go deleted file mode 100644 index 530fb89..0000000 --- a/internal/handler/app/auth/registerHandler.go +++ /dev/null @@ -1,43 +0,0 @@ -package auth - -import ( - "time" - - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" - "github.com/perfect-panel/ppanel-server/pkg/turnstile" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -// Register -func RegisterHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.AppAuthRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - // get client ip - if svcCtx.Config.Verify.RegisterVerify { - verifyTurns := turnstile.New(turnstile.Config{ - Secret: svcCtx.Config.Verify.TurnstileSecret, - Timeout: 3 * time.Second, - }) - if verify, err := verifyTurns.Verify(c, req.CfToken, c.ClientIP()); err != nil || !verify { - err = errors.Wrapf(xerr.NewErrCode(xerr.TooManyRequests), "error: %v, verify: %v", err, verify) - result.HttpResult(c, nil, err) - return - } - } - - l := auth.NewRegisterLogic(c, svcCtx) - resp, err := l.Register(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/auth/resetPasswordHandler.go b/internal/handler/app/auth/resetPasswordHandler.go deleted file mode 100644 index c1c8dd2..0000000 --- a/internal/handler/app/auth/resetPasswordHandler.go +++ /dev/null @@ -1,41 +0,0 @@ -package auth - -import ( - "time" - - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" - "github.com/perfect-panel/ppanel-server/pkg/turnstile" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -// Reset Password -func ResetPasswordHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.AppAuthRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - if svcCtx.Config.Verify.ResetPasswordVerify { - verifyTurns := turnstile.New(turnstile.Config{ - Secret: svcCtx.Config.Verify.TurnstileSecret, - Timeout: 3 * time.Second, - }) - if verify, err := verifyTurns.Verify(c, req.CfToken, c.ClientIP()); err != nil || !verify { - err = errors.Wrapf(xerr.NewErrCode(xerr.TooManyRequests), "error: %v, verify: %v", err, verify) - result.HttpResult(c, nil, err) - return - } - } - l := auth.NewResetPasswordLogic(c, svcCtx) - resp, err := l.ResetPassword(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/document/querydocumentdetailhandler.go b/internal/handler/app/document/querydocumentdetailhandler.go deleted file mode 100644 index 36050e4..0000000 --- a/internal/handler/app/document/querydocumentdetailhandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package document - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/document" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get document detail -func QueryDocumentDetailHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.QueryDocumentDetailRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := document.NewQueryDocumentDetailLogic(c.Request.Context(), svcCtx) - resp, err := l.QueryDocumentDetail(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/document/querydocumentlisthandler.go b/internal/handler/app/document/querydocumentlisthandler.go deleted file mode 100644 index 842704a..0000000 --- a/internal/handler/app/document/querydocumentlisthandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package document - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/document" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get document list -func QueryDocumentListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := document.NewQueryDocumentListLogic(c.Request.Context(), svcCtx) - resp, err := l.QueryDocumentList() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/node/getNodeListHandler.go b/internal/handler/app/node/getNodeListHandler.go deleted file mode 100644 index 8e0b3f0..0000000 --- a/internal/handler/app/node/getNodeListHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package node - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/node" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get Node list -func GetNodeListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.AppUserSubscbribeNodeRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := node.NewGetNodeListLogic(c.Request.Context(), svcCtx) - resp, err := l.GetNodeList(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/node/getRuleGroupListHandler.go b/internal/handler/app/node/getRuleGroupListHandler.go deleted file mode 100644 index 1b08f0a..0000000 --- a/internal/handler/app/node/getRuleGroupListHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package node - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/node" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get rule group list -func GetRuleGroupListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := node.NewGetRuleGroupListLogic(c.Request.Context(), svcCtx) - resp, err := l.GetRuleGroupList() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/order/checkoutorderhandler.go b/internal/handler/app/order/checkoutorderhandler.go deleted file mode 100644 index ec23ebc..0000000 --- a/internal/handler/app/order/checkoutorderhandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package order - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Checkout order -func CheckoutOrderHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.CheckoutOrderRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := order.NewCheckoutOrderLogic(c.Request.Context(), svcCtx) - resp, err := l.CheckoutOrder(&req, c.Request.Host) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/order/closeorderhandler.go b/internal/handler/app/order/closeorderhandler.go deleted file mode 100644 index 474cfdc..0000000 --- a/internal/handler/app/order/closeorderhandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package order - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Close order -func CloseOrderHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.CloseOrderRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := order.NewCloseOrderLogic(c.Request.Context(), svcCtx) - err := l.CloseOrder(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/app/order/precreateorderhandler.go b/internal/handler/app/order/precreateorderhandler.go deleted file mode 100644 index e7a3b63..0000000 --- a/internal/handler/app/order/precreateorderhandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package order - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Pre create order -func PreCreateOrderHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.PurchaseOrderRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := order.NewPreCreateOrderLogic(c.Request.Context(), svcCtx) - resp, err := l.PreCreateOrder(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/order/purchasehandler.go b/internal/handler/app/order/purchasehandler.go deleted file mode 100644 index 99e3d45..0000000 --- a/internal/handler/app/order/purchasehandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package order - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// purchase Subscription -func PurchaseHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.PurchaseOrderRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := order.NewPurchaseLogic(c.Request.Context(), svcCtx) - resp, err := l.Purchase(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/order/queryorderdetailhandler.go b/internal/handler/app/order/queryorderdetailhandler.go deleted file mode 100644 index 3adc5b0..0000000 --- a/internal/handler/app/order/queryorderdetailhandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package order - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get order -func QueryOrderDetailHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.QueryOrderDetailRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := order.NewQueryOrderDetailLogic(c.Request.Context(), svcCtx) - resp, err := l.QueryOrderDetail(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/order/queryorderlisthandler.go b/internal/handler/app/order/queryorderlisthandler.go deleted file mode 100644 index 7a37db3..0000000 --- a/internal/handler/app/order/queryorderlisthandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package order - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get order list -func QueryOrderListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.QueryOrderListRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := order.NewQueryOrderListLogic(c.Request.Context(), svcCtx) - resp, err := l.QueryOrderList(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/order/rechargehandler.go b/internal/handler/app/order/rechargehandler.go deleted file mode 100644 index 56fc11c..0000000 --- a/internal/handler/app/order/rechargehandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package order - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Recharge -func RechargeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.RechargeOrderRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := order.NewRechargeLogic(c.Request.Context(), svcCtx) - resp, err := l.Recharge(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/order/renewalhandler.go b/internal/handler/app/order/renewalhandler.go deleted file mode 100644 index 9482181..0000000 --- a/internal/handler/app/order/renewalhandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package order - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Renewal Subscription -func RenewalHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.RenewalOrderRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := order.NewRenewalLogic(c.Request.Context(), svcCtx) - resp, err := l.Renewal(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/order/resettraffichandler.go b/internal/handler/app/order/resettraffichandler.go deleted file mode 100644 index bdffad3..0000000 --- a/internal/handler/app/order/resettraffichandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package order - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Reset traffic -func ResetTrafficHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.ResetTrafficOrderRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := order.NewResetTrafficLogic(c.Request.Context(), svcCtx) - resp, err := l.ResetTraffic(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/payment/getavailablepaymentmethodshandler.go b/internal/handler/app/payment/getavailablepaymentmethodshandler.go deleted file mode 100644 index 57e7495..0000000 --- a/internal/handler/app/payment/getavailablepaymentmethodshandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package payment - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/payment" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get available payment methods -func GetAvailablePaymentMethodsHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := payment.NewGetAvailablePaymentMethodsLogic(c.Request.Context(), svcCtx) - resp, err := l.GetAvailablePaymentMethods() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/subscribe/queryApplicationConfigHandler.go b/internal/handler/app/subscribe/queryApplicationConfigHandler.go deleted file mode 100644 index f663047..0000000 --- a/internal/handler/app/subscribe/queryApplicationConfigHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package subscribe - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get application config -func QueryApplicationConfigHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := subscribe.NewQueryApplicationConfigLogic(c.Request.Context(), svcCtx) - resp, err := l.QueryApplicationConfig() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/subscribe/querySubscribeGroupListHandler.go b/internal/handler/app/subscribe/querySubscribeGroupListHandler.go deleted file mode 100644 index 8403fe8..0000000 --- a/internal/handler/app/subscribe/querySubscribeGroupListHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package subscribe - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get subscribe group list -func QuerySubscribeGroupListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := subscribe.NewQuerySubscribeGroupListLogic(c.Request.Context(), svcCtx) - resp, err := l.QuerySubscribeGroupList() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/subscribe/querySubscribeListHandler.go b/internal/handler/app/subscribe/querySubscribeListHandler.go deleted file mode 100644 index d9f427d..0000000 --- a/internal/handler/app/subscribe/querySubscribeListHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package subscribe - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get subscribe list -func QuerySubscribeListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := subscribe.NewQuerySubscribeListLogic(c.Request.Context(), svcCtx) - resp, err := l.QuerySubscribeList() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/subscribe/queryUserAlreadySubscribeHandler.go b/internal/handler/app/subscribe/queryUserAlreadySubscribeHandler.go deleted file mode 100644 index b5324dd..0000000 --- a/internal/handler/app/subscribe/queryUserAlreadySubscribeHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package subscribe - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get Already subscribed to package -func QueryUserAlreadySubscribeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := subscribe.NewQueryUserAlreadySubscribeLogic(c.Request.Context(), svcCtx) - resp, err := l.QueryUserAlreadySubscribe() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/subscribe/queryUserAvailableUserSubscribeHandler.go b/internal/handler/app/subscribe/queryUserAvailableUserSubscribeHandler.go deleted file mode 100644 index 9f87808..0000000 --- a/internal/handler/app/subscribe/queryUserAvailableUserSubscribeHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package subscribe - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get Available subscriptions for users -func QueryUserAvailableUserSubscribeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.AppUserSubscribeRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := subscribe.NewQueryUserAvailableUserSubscribeLogic(c.Request.Context(), svcCtx) - resp, err := l.QueryUserAvailableUserSubscribe(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/subscribe/resetUserSubscribePeriodHandler.go b/internal/handler/app/subscribe/resetUserSubscribePeriodHandler.go deleted file mode 100644 index fa687b9..0000000 --- a/internal/handler/app/subscribe/resetUserSubscribePeriodHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package subscribe - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Reset user subscription period -func ResetUserSubscribePeriodHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.UserSubscribeResetPeriodRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := subscribe.NewResetUserSubscribePeriodLogic(c.Request.Context(), svcCtx) - resp, err := l.ResetUserSubscribePeriod(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/user/deleteAccountHandler.go b/internal/handler/app/user/deleteAccountHandler.go deleted file mode 100644 index b7d5337..0000000 --- a/internal/handler/app/user/deleteAccountHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package user - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Delete Account -func DeleteAccountHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.DeleteAccountRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := user.NewDeleteAccountLogic(c.Request.Context(), svcCtx) - err := l.DeleteAccount(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/app/user/getuseronlinetimestatisticshandler.go b/internal/handler/app/user/getuseronlinetimestatisticshandler.go deleted file mode 100644 index 678ffa5..0000000 --- a/internal/handler/app/user/getuseronlinetimestatisticshandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package user - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get user online time total -func GetUserOnlineTimeStatisticsHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := user.NewGetUserOnlineTimeStatisticsLogic(c.Request.Context(), svcCtx) - resp, err := l.GetUserOnlineTimeStatistics() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/user/getusersubscribetrafficlogshandler.go b/internal/handler/app/user/getusersubscribetrafficlogshandler.go deleted file mode 100644 index b1cc669..0000000 --- a/internal/handler/app/user/getusersubscribetrafficlogshandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package user - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get user subcribe traffic logs -func GetUserSubscribeTrafficLogsHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.GetUserSubscribeTrafficLogsRequest - _ = c.BindQuery(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := user.NewGetUserSubscribeTrafficLogsLogic(c.Request.Context(), svcCtx) - resp, err := l.GetUserSubscribeTrafficLogs(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/user/queryUserInfoHandler.go b/internal/handler/app/user/queryUserInfoHandler.go deleted file mode 100644 index c955983..0000000 --- a/internal/handler/app/user/queryUserInfoHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package user - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// query user info -func QueryUserInfoHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := user.NewQueryUserInfoLogic(c.Request.Context(), svcCtx) - resp, err := l.QueryUserInfo() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/user/queryuseraffiliatehandler.go b/internal/handler/app/user/queryuseraffiliatehandler.go deleted file mode 100644 index c5bfd2f..0000000 --- a/internal/handler/app/user/queryuseraffiliatehandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package user - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Query User Affiliate Count -func QueryUserAffiliateHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := user.NewQueryUserAffiliateLogic(c.Request.Context(), svcCtx) - resp, err := l.QueryUserAffiliate() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/user/queryuseraffiliatelisthandler.go b/internal/handler/app/user/queryuseraffiliatelisthandler.go deleted file mode 100644 index 6e2f6a5..0000000 --- a/internal/handler/app/user/queryuseraffiliatelisthandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package user - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Query User Affiliate List -func QueryUserAffiliateListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.QueryUserAffiliateListRequest - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := user.NewQueryUserAffiliateListLogic(c.Request.Context(), svcCtx) - resp, err := l.QueryUserAffiliateList(&req) - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/app/user/updatePasswordHandler.go b/internal/handler/app/user/updatePasswordHandler.go deleted file mode 100644 index 532612e..0000000 --- a/internal/handler/app/user/updatePasswordHandler.go +++ /dev/null @@ -1,26 +0,0 @@ -package user - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Update Password -func UpdatePasswordHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - var req types.UpdatePasswordRequeset - _ = c.ShouldBind(&req) - validateErr := svcCtx.Validate(&req) - if validateErr != nil { - result.ParamErrorResult(c, validateErr) - return - } - - l := user.NewUpdatePasswordLogic(c.Request.Context(), svcCtx) - err := l.UpdatePassword(&req) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/app/ws/appWsHandler.go b/internal/handler/app/ws/appWsHandler.go deleted file mode 100644 index 6e90b67..0000000 --- a/internal/handler/app/ws/appWsHandler.go +++ /dev/null @@ -1,20 +0,0 @@ -package ws - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/app/ws" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// App heartbeat -func AppWsHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - ctx := c.Request.Context() - - // Logic: App heartbeat - l := ws.NewAppWsLogic(ctx, svcCtx) - err := l.AppWs(c.Writer, c.Request, c.Param("userid"), c.Param("identifier")) - result.HttpResult(c, nil, err) - } -} diff --git a/internal/handler/auth/checkUserHandler.go b/internal/handler/auth/checkUserHandler.go index 840642b..86c3a7d 100644 --- a/internal/handler/auth/checkUserHandler.go +++ b/internal/handler/auth/checkUserHandler.go @@ -2,10 +2,10 @@ package auth import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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" ) // Check user is exist diff --git a/internal/handler/auth/checkUserTelephoneHandler.go b/internal/handler/auth/checkUserTelephoneHandler.go index c438b84..649eba9 100644 --- a/internal/handler/auth/checkUserTelephoneHandler.go +++ b/internal/handler/auth/checkUserTelephoneHandler.go @@ -2,10 +2,10 @@ package auth import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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" ) // Check user telephone is exist diff --git a/internal/handler/auth/deviceLoginHandler.go b/internal/handler/auth/deviceLoginHandler.go new file mode 100644 index 0000000..6a772bf --- /dev/null +++ b/internal/handler/auth/deviceLoginHandler.go @@ -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) + } +} diff --git a/internal/handler/auth/oauth/appleLoginCallbackHandler.go b/internal/handler/auth/oauth/appleLoginCallbackHandler.go index e69764d..fe256ca 100644 --- a/internal/handler/auth/oauth/appleLoginCallbackHandler.go +++ b/internal/handler/auth/oauth/appleLoginCallbackHandler.go @@ -4,10 +4,10 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/auth/oauth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/auth/oauth" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Apple Login Callback diff --git a/internal/handler/auth/oauth/oAuthLoginGetTokenHandler.go b/internal/handler/auth/oauth/oAuthLoginGetTokenHandler.go index 384dafb..a08f475 100644 --- a/internal/handler/auth/oauth/oAuthLoginGetTokenHandler.go +++ b/internal/handler/auth/oauth/oAuthLoginGetTokenHandler.go @@ -2,10 +2,10 @@ package oauth import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/auth/oauth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/auth/oauth" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // OAuth login get token diff --git a/internal/handler/auth/oauth/oAuthLoginHandler.go b/internal/handler/auth/oauth/oAuthLoginHandler.go index 1653f3b..588f0d6 100644 --- a/internal/handler/auth/oauth/oAuthLoginHandler.go +++ b/internal/handler/auth/oauth/oAuthLoginHandler.go @@ -2,10 +2,10 @@ package oauth import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/auth/oauth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/auth/oauth" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // OAuth login diff --git a/internal/handler/auth/resetPasswordHandler.go b/internal/handler/auth/resetPasswordHandler.go index 73848b2..d4edc9b 100644 --- a/internal/handler/auth/resetPasswordHandler.go +++ b/internal/handler/auth/resetPasswordHandler.go @@ -4,12 +4,12 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" - "github.com/perfect-panel/ppanel-server/pkg/turnstile" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" + "github.com/perfect-panel/server/pkg/turnstile" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) diff --git a/internal/handler/auth/telephoneLoginHandler.go b/internal/handler/auth/telephoneLoginHandler.go index 798b1cd..44c1b53 100644 --- a/internal/handler/auth/telephoneLoginHandler.go +++ b/internal/handler/auth/telephoneLoginHandler.go @@ -4,12 +4,12 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" - "github.com/perfect-panel/ppanel-server/pkg/turnstile" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" + "github.com/perfect-panel/server/pkg/turnstile" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) diff --git a/internal/handler/auth/telephoneResetPasswordHandler.go b/internal/handler/auth/telephoneResetPasswordHandler.go index 4b870c0..16a5105 100644 --- a/internal/handler/auth/telephoneResetPasswordHandler.go +++ b/internal/handler/auth/telephoneResetPasswordHandler.go @@ -4,12 +4,12 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" - "github.com/perfect-panel/ppanel-server/pkg/turnstile" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" + "github.com/perfect-panel/server/pkg/turnstile" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) diff --git a/internal/handler/auth/telephoneUserRegisterHandler.go b/internal/handler/auth/telephoneUserRegisterHandler.go index bcaef82..45a7ba8 100644 --- a/internal/handler/auth/telephoneUserRegisterHandler.go +++ b/internal/handler/auth/telephoneUserRegisterHandler.go @@ -4,12 +4,12 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" - "github.com/perfect-panel/ppanel-server/pkg/turnstile" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" + "github.com/perfect-panel/server/pkg/turnstile" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) @@ -25,6 +25,7 @@ func TelephoneUserRegisterHandler(svcCtx *svc.ServiceContext) func(c *gin.Contex } // get client ip req.IP = c.ClientIP() + req.UserAgent = c.Request.UserAgent() if svcCtx.Config.Verify.RegisterVerify { verifyTurns := turnstile.New(turnstile.Config{ Secret: svcCtx.Config.Verify.TurnstileSecret, diff --git a/internal/handler/auth/userLoginHandler.go b/internal/handler/auth/userLoginHandler.go index 1e857c3..20eff59 100644 --- a/internal/handler/auth/userLoginHandler.go +++ b/internal/handler/auth/userLoginHandler.go @@ -4,12 +4,12 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" - "github.com/perfect-panel/ppanel-server/pkg/turnstile" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" + "github.com/perfect-panel/server/pkg/turnstile" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) diff --git a/internal/handler/auth/userRegisterHandler.go b/internal/handler/auth/userRegisterHandler.go index 9bdcd99..ea40223 100644 --- a/internal/handler/auth/userRegisterHandler.go +++ b/internal/handler/auth/userRegisterHandler.go @@ -4,12 +4,12 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" - "github.com/perfect-panel/ppanel-server/pkg/turnstile" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" + "github.com/perfect-panel/server/pkg/turnstile" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) @@ -20,6 +20,7 @@ func UserRegisterHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { _ = c.ShouldBind(&req) // get client ip req.IP = c.ClientIP() + req.UserAgent = c.Request.UserAgent() if svcCtx.Config.Verify.RegisterVerify { verifyTurns := turnstile.New(turnstile.Config{ Secret: svcCtx.Config.Verify.TurnstileSecret, diff --git a/internal/handler/common/checkverificationcodehandler.go b/internal/handler/common/checkverificationcodehandler.go index 0bdf6be..70c743f 100644 --- a/internal/handler/common/checkverificationcodehandler.go +++ b/internal/handler/common/checkverificationcodehandler.go @@ -2,10 +2,10 @@ package common import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/common" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/common" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Check verification code diff --git a/internal/handler/common/getAdsHandler.go b/internal/handler/common/getAdsHandler.go index 5eba200..f2c2192 100644 --- a/internal/handler/common/getAdsHandler.go +++ b/internal/handler/common/getAdsHandler.go @@ -2,10 +2,10 @@ package common import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/common" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/common" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get Ads diff --git a/internal/handler/common/getApplicationHandler.go b/internal/handler/common/getApplicationHandler.go deleted file mode 100644 index fa91a77..0000000 --- a/internal/handler/common/getApplicationHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package common - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/common" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get Tos Content -func GetApplicationHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := common.NewGetApplicationLogic(c.Request.Context(), svcCtx) - resp, err := l.GetApplication() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/common/getClientHandler.go b/internal/handler/common/getClientHandler.go new file mode 100644 index 0000000..e40b555 --- /dev/null +++ b/internal/handler/common/getClientHandler.go @@ -0,0 +1,18 @@ +package common + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/common" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" +) + +// Get Client +func GetClientHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + + l := common.NewGetClientLogic(c.Request.Context(), svcCtx) + resp, err := l.GetClient() + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/common/getGlobalConfigHandler.go b/internal/handler/common/getGlobalConfigHandler.go index 0d45405..9f520f4 100644 --- a/internal/handler/common/getGlobalConfigHandler.go +++ b/internal/handler/common/getGlobalConfigHandler.go @@ -2,9 +2,9 @@ package common import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/common" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/common" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get global config diff --git a/internal/handler/common/getPrivacyPolicyHandler.go b/internal/handler/common/getPrivacyPolicyHandler.go index 09f6c6e..4674d23 100644 --- a/internal/handler/common/getPrivacyPolicyHandler.go +++ b/internal/handler/common/getPrivacyPolicyHandler.go @@ -2,9 +2,9 @@ package common import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/common" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/common" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get Privacy Policy diff --git a/internal/handler/common/getStatHandler.go b/internal/handler/common/getStatHandler.go index a370fd3..9e6b4fa 100644 --- a/internal/handler/common/getStatHandler.go +++ b/internal/handler/common/getStatHandler.go @@ -2,9 +2,9 @@ package common import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/common" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/common" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get stat diff --git a/internal/handler/common/getSubscriptionHandler.go b/internal/handler/common/getSubscriptionHandler.go deleted file mode 100644 index a63e221..0000000 --- a/internal/handler/common/getSubscriptionHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package common - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/common" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get Subscription -func GetSubscriptionHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := common.NewGetSubscriptionLogic(c.Request.Context(), svcCtx) - resp, err := l.GetSubscription() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/common/getTosHandler.go b/internal/handler/common/getTosHandler.go index 2979881..caa5c5a 100644 --- a/internal/handler/common/getTosHandler.go +++ b/internal/handler/common/getTosHandler.go @@ -2,9 +2,9 @@ package common import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/common" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/common" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get Tos Content diff --git a/internal/handler/common/sendEmailCodeHandler.go b/internal/handler/common/sendEmailCodeHandler.go index 7c9d372..09b1c94 100644 --- a/internal/handler/common/sendEmailCodeHandler.go +++ b/internal/handler/common/sendEmailCodeHandler.go @@ -2,10 +2,10 @@ package common import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/common" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/common" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get verification code diff --git a/internal/handler/common/sendSmsCodeHandler.go b/internal/handler/common/sendSmsCodeHandler.go index 2b7220d..097b99f 100644 --- a/internal/handler/common/sendSmsCodeHandler.go +++ b/internal/handler/common/sendSmsCodeHandler.go @@ -2,10 +2,10 @@ package common import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/common" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/common" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get sms verification code diff --git a/internal/handler/notify.go b/internal/handler/notify.go index a73bd70..3055dcd 100644 --- a/internal/handler/notify.go +++ b/internal/handler/notify.go @@ -2,9 +2,9 @@ package handler import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/handler/notify" - "github.com/perfect-panel/ppanel-server/internal/middleware" - "github.com/perfect-panel/ppanel-server/internal/svc" + "github.com/perfect-panel/server/internal/handler/notify" + "github.com/perfect-panel/server/internal/middleware" + "github.com/perfect-panel/server/internal/svc" ) func RegisterNotifyHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { diff --git a/internal/handler/notify/paymentNotifyHandler.go b/internal/handler/notify/paymentNotifyHandler.go index f84ad00..cd7d8b9 100644 --- a/internal/handler/notify/paymentNotifyHandler.go +++ b/internal/handler/notify/paymentNotifyHandler.go @@ -4,15 +4,15 @@ import ( "fmt" "net/http" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/notify" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/payment" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/notify" + "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/payment" + "github.com/perfect-panel/server/pkg/result" ) // PaymentNotifyHandler Payment Notify @@ -26,7 +26,7 @@ func PaymentNotifyHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { } switch payment.ParsePlatform(platform) { - case payment.EPay: + case payment.EPay, payment.CryptoSaaS: req := &types.EPayNotifyRequest{} if err := c.ShouldBind(req); err != nil { result.HttpResult(c, nil, err) diff --git a/internal/handler/public/announcement/queryAnnouncementHandler.go b/internal/handler/public/announcement/queryAnnouncementHandler.go index dd2a8e8..ae222f0 100644 --- a/internal/handler/public/announcement/queryAnnouncementHandler.go +++ b/internal/handler/public/announcement/queryAnnouncementHandler.go @@ -2,10 +2,10 @@ package announcement import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/announcement" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/announcement" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Query announcement diff --git a/internal/handler/public/document/queryDocumentDetailHandler.go b/internal/handler/public/document/queryDocumentDetailHandler.go index 7b2524b..92a2168 100644 --- a/internal/handler/public/document/queryDocumentDetailHandler.go +++ b/internal/handler/public/document/queryDocumentDetailHandler.go @@ -2,10 +2,10 @@ package document import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/document" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/document" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get document detail diff --git a/internal/handler/public/document/queryDocumentListHandler.go b/internal/handler/public/document/queryDocumentListHandler.go index 9a57040..9344d62 100644 --- a/internal/handler/public/document/queryDocumentListHandler.go +++ b/internal/handler/public/document/queryDocumentListHandler.go @@ -2,9 +2,9 @@ package document import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/document" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/document" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get document list diff --git a/internal/handler/public/order/closeOrderHandler.go b/internal/handler/public/order/closeOrderHandler.go index c0c9120..f92c7c8 100644 --- a/internal/handler/public/order/closeOrderHandler.go +++ b/internal/handler/public/order/closeOrderHandler.go @@ -2,10 +2,10 @@ package order import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/order" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Close order diff --git a/internal/handler/public/order/preCreateOrderHandler.go b/internal/handler/public/order/preCreateOrderHandler.go index bca9773..1e5f505 100644 --- a/internal/handler/public/order/preCreateOrderHandler.go +++ b/internal/handler/public/order/preCreateOrderHandler.go @@ -2,10 +2,10 @@ package order import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/order" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Pre create order diff --git a/internal/handler/public/order/purchaseHandler.go b/internal/handler/public/order/purchaseHandler.go index a7eb606..3dcfe40 100644 --- a/internal/handler/public/order/purchaseHandler.go +++ b/internal/handler/public/order/purchaseHandler.go @@ -2,10 +2,10 @@ package order import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/order" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // purchase Subscription diff --git a/internal/handler/public/order/queryOrderDetailHandler.go b/internal/handler/public/order/queryOrderDetailHandler.go index dcc3b9f..ff65632 100644 --- a/internal/handler/public/order/queryOrderDetailHandler.go +++ b/internal/handler/public/order/queryOrderDetailHandler.go @@ -2,10 +2,10 @@ package order import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/order" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get order diff --git a/internal/handler/public/order/queryOrderListHandler.go b/internal/handler/public/order/queryOrderListHandler.go index 4ecf7ce..e8bf02b 100644 --- a/internal/handler/public/order/queryOrderListHandler.go +++ b/internal/handler/public/order/queryOrderListHandler.go @@ -2,10 +2,10 @@ package order import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/order" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get order list diff --git a/internal/handler/public/order/rechargeHandler.go b/internal/handler/public/order/rechargeHandler.go index 3807585..e64da16 100644 --- a/internal/handler/public/order/rechargeHandler.go +++ b/internal/handler/public/order/rechargeHandler.go @@ -2,10 +2,10 @@ package order import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/order" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Recharge diff --git a/internal/handler/public/order/renewalHandler.go b/internal/handler/public/order/renewalHandler.go index 8cd6c5a..e9d769b 100644 --- a/internal/handler/public/order/renewalHandler.go +++ b/internal/handler/public/order/renewalHandler.go @@ -2,10 +2,10 @@ package order import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/order" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Renewal Subscription diff --git a/internal/handler/public/order/resetTrafficHandler.go b/internal/handler/public/order/resetTrafficHandler.go index d4cb5af..ae5d9a8 100644 --- a/internal/handler/public/order/resetTrafficHandler.go +++ b/internal/handler/public/order/resetTrafficHandler.go @@ -2,10 +2,10 @@ package order import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/order" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Reset traffic diff --git a/internal/handler/public/payment/getAvailablePaymentMethodsHandler.go b/internal/handler/public/payment/getAvailablePaymentMethodsHandler.go index 96aa03d..2bdbeb2 100644 --- a/internal/handler/public/payment/getAvailablePaymentMethodsHandler.go +++ b/internal/handler/public/payment/getAvailablePaymentMethodsHandler.go @@ -2,9 +2,9 @@ package payment import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/payment" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/payment" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get available payment methods diff --git a/internal/handler/public/portal/getAvailablePaymentMethodsHandler.go b/internal/handler/public/portal/getAvailablePaymentMethodsHandler.go index cd6a19a..8955117 100644 --- a/internal/handler/public/portal/getAvailablePaymentMethodsHandler.go +++ b/internal/handler/public/portal/getAvailablePaymentMethodsHandler.go @@ -2,9 +2,9 @@ package portal import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/portal" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/portal" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get available payment methods diff --git a/internal/handler/public/portal/getSubscriptionHandler.go b/internal/handler/public/portal/getSubscriptionHandler.go index 695ceba..6d7f735 100644 --- a/internal/handler/public/portal/getSubscriptionHandler.go +++ b/internal/handler/public/portal/getSubscriptionHandler.go @@ -2,16 +2,25 @@ package portal import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/portal" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/portal" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get Subscription func GetSubscriptionHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { return func(c *gin.Context) { + var req types.GetSubscriptionRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + l := portal.NewGetSubscriptionLogic(c.Request.Context(), svcCtx) - resp, err := l.GetSubscription() + resp, err := l.GetSubscription(&req) result.HttpResult(c, resp, err) } } diff --git a/internal/handler/public/portal/prePurchaseOrderHandler.go b/internal/handler/public/portal/prePurchaseOrderHandler.go index 37650b2..511f97c 100644 --- a/internal/handler/public/portal/prePurchaseOrderHandler.go +++ b/internal/handler/public/portal/prePurchaseOrderHandler.go @@ -2,10 +2,10 @@ package portal import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/portal" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/portal" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Pre Purchase Order diff --git a/internal/handler/public/portal/purchaseCheckoutHandler.go b/internal/handler/public/portal/purchaseCheckoutHandler.go index ec06c9e..0e34d38 100644 --- a/internal/handler/public/portal/purchaseCheckoutHandler.go +++ b/internal/handler/public/portal/purchaseCheckoutHandler.go @@ -2,13 +2,13 @@ package portal import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/portal" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/portal" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) -// Purchase Checkout +// PurchaseCheckoutHandler Purchase Checkout func PurchaseCheckoutHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { return func(c *gin.Context) { var req types.CheckoutOrderRequest diff --git a/internal/handler/public/portal/purchaseHandler.go b/internal/handler/public/portal/purchaseHandler.go index a483050..1f56f83 100644 --- a/internal/handler/public/portal/purchaseHandler.go +++ b/internal/handler/public/portal/purchaseHandler.go @@ -2,10 +2,10 @@ package portal import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/portal" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/portal" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Purchase subscription diff --git a/internal/handler/public/portal/queryPurchaseOrderHandler.go b/internal/handler/public/portal/queryPurchaseOrderHandler.go index 2e7cf2e..f134647 100644 --- a/internal/handler/public/portal/queryPurchaseOrderHandler.go +++ b/internal/handler/public/portal/queryPurchaseOrderHandler.go @@ -2,10 +2,10 @@ package portal import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/portal" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/portal" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Query Purchase Order diff --git a/internal/handler/public/subscribe/queryApplicationConfigHandler.go b/internal/handler/public/subscribe/queryApplicationConfigHandler.go deleted file mode 100644 index 7cb91c5..0000000 --- a/internal/handler/public/subscribe/queryApplicationConfigHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -package subscribe - -import ( - "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" -) - -// Get application config -func QueryApplicationConfigHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { - return func(c *gin.Context) { - - l := subscribe.NewQueryApplicationConfigLogic(c.Request.Context(), svcCtx) - resp, err := l.QueryApplicationConfig() - result.HttpResult(c, resp, err) - } -} diff --git a/internal/handler/public/subscribe/querySubscribeGroupListHandler.go b/internal/handler/public/subscribe/querySubscribeGroupListHandler.go index 8b48fc1..eca7db7 100644 --- a/internal/handler/public/subscribe/querySubscribeGroupListHandler.go +++ b/internal/handler/public/subscribe/querySubscribeGroupListHandler.go @@ -2,9 +2,9 @@ package subscribe import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/subscribe" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get subscribe group list diff --git a/internal/handler/public/subscribe/querySubscribeListHandler.go b/internal/handler/public/subscribe/querySubscribeListHandler.go index 551725d..6a68a46 100644 --- a/internal/handler/public/subscribe/querySubscribeListHandler.go +++ b/internal/handler/public/subscribe/querySubscribeListHandler.go @@ -2,17 +2,25 @@ package subscribe import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/subscribe" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) -// Get subscribe list +// QuerySubscribeListHandler Get subscribe list func QuerySubscribeListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { return func(c *gin.Context) { + var req types.QuerySubscribeListRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } l := subscribe.NewQuerySubscribeListLogic(c.Request.Context(), svcCtx) - resp, err := l.QuerySubscribeList() + resp, err := l.QuerySubscribeList(&req) result.HttpResult(c, resp, err) } } diff --git a/internal/handler/public/ticket/createUserTicketFollowHandler.go b/internal/handler/public/ticket/createUserTicketFollowHandler.go index b94334e..720e55c 100644 --- a/internal/handler/public/ticket/createUserTicketFollowHandler.go +++ b/internal/handler/public/ticket/createUserTicketFollowHandler.go @@ -2,10 +2,10 @@ package ticket import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/ticket" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/ticket" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Create ticket follow diff --git a/internal/handler/public/ticket/createUserTicketHandler.go b/internal/handler/public/ticket/createUserTicketHandler.go index 2cbae74..194b37b 100644 --- a/internal/handler/public/ticket/createUserTicketHandler.go +++ b/internal/handler/public/ticket/createUserTicketHandler.go @@ -2,10 +2,10 @@ package ticket import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/ticket" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/ticket" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Create ticket diff --git a/internal/handler/public/ticket/getUserTicketDetailsHandler.go b/internal/handler/public/ticket/getUserTicketDetailsHandler.go index 6230664..e664376 100644 --- a/internal/handler/public/ticket/getUserTicketDetailsHandler.go +++ b/internal/handler/public/ticket/getUserTicketDetailsHandler.go @@ -2,10 +2,10 @@ package ticket import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/ticket" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/ticket" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get ticket detail diff --git a/internal/handler/public/ticket/getUserTicketListHandler.go b/internal/handler/public/ticket/getUserTicketListHandler.go index 9b86d0e..3e3aed5 100644 --- a/internal/handler/public/ticket/getUserTicketListHandler.go +++ b/internal/handler/public/ticket/getUserTicketListHandler.go @@ -2,10 +2,10 @@ package ticket import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/ticket" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/ticket" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Get ticket list diff --git a/internal/handler/public/ticket/updateUserTicketStatusHandler.go b/internal/handler/public/ticket/updateUserTicketStatusHandler.go index 12e7c82..cb71fb5 100644 --- a/internal/handler/public/ticket/updateUserTicketStatusHandler.go +++ b/internal/handler/public/ticket/updateUserTicketStatusHandler.go @@ -2,10 +2,10 @@ package ticket import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/ticket" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/ticket" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Update ticket status diff --git a/internal/handler/public/user/bindOAuthCallbackHandler.go b/internal/handler/public/user/bindOAuthCallbackHandler.go index 67cc030..adf7927 100644 --- a/internal/handler/public/user/bindOAuthCallbackHandler.go +++ b/internal/handler/public/user/bindOAuthCallbackHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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" ) // Bind OAuth Callback diff --git a/internal/handler/public/user/bindOAuthHandler.go b/internal/handler/public/user/bindOAuthHandler.go index f80dfa9..3b31d4a 100644 --- a/internal/handler/public/user/bindOAuthHandler.go +++ b/internal/handler/public/user/bindOAuthHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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" ) // Bind OAuth diff --git a/internal/handler/public/user/bindTelegramHandler.go b/internal/handler/public/user/bindTelegramHandler.go index 3ae8630..2e1027d 100644 --- a/internal/handler/public/user/bindTelegramHandler.go +++ b/internal/handler/public/user/bindTelegramHandler.go @@ -2,9 +2,9 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Bind Telegram diff --git a/internal/handler/public/user/getDeviceListHandler.go b/internal/handler/public/user/getDeviceListHandler.go new file mode 100644 index 0000000..deb2e3a --- /dev/null +++ b/internal/handler/public/user/getDeviceListHandler.go @@ -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) + } +} diff --git a/internal/handler/public/user/getLoginLogHandler.go b/internal/handler/public/user/getLoginLogHandler.go index 9a07df0..0188f8b 100644 --- a/internal/handler/public/user/getLoginLogHandler.go +++ b/internal/handler/public/user/getLoginLogHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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" ) // Get Login Log diff --git a/internal/handler/public/user/getOAuthMethodsHandler.go b/internal/handler/public/user/getOAuthMethodsHandler.go index 5e62f51..2e5fb24 100644 --- a/internal/handler/public/user/getOAuthMethodsHandler.go +++ b/internal/handler/public/user/getOAuthMethodsHandler.go @@ -2,9 +2,9 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Get OAuth Methods diff --git a/internal/handler/public/user/getSubscribeLogHandler.go b/internal/handler/public/user/getSubscribeLogHandler.go index eec3314..b417ef2 100644 --- a/internal/handler/public/user/getSubscribeLogHandler.go +++ b/internal/handler/public/user/getSubscribeLogHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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" ) // Get Subscribe Log diff --git a/internal/handler/public/user/preUnsubscribeHandler.go b/internal/handler/public/user/preUnsubscribeHandler.go index a2f76b8..6f02968 100644 --- a/internal/handler/public/user/preUnsubscribeHandler.go +++ b/internal/handler/public/user/preUnsubscribeHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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" ) // Pre Unsubscribe diff --git a/internal/handler/public/user/queryUserAffiliateHandler.go b/internal/handler/public/user/queryUserAffiliateHandler.go index 79f7109..3a2ee8e 100644 --- a/internal/handler/public/user/queryUserAffiliateHandler.go +++ b/internal/handler/public/user/queryUserAffiliateHandler.go @@ -2,9 +2,9 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Query User Affiliate Count diff --git a/internal/handler/public/user/queryUserAffiliateListHandler.go b/internal/handler/public/user/queryUserAffiliateListHandler.go index 8cca91b..7882ec5 100644 --- a/internal/handler/public/user/queryUserAffiliateListHandler.go +++ b/internal/handler/public/user/queryUserAffiliateListHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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 User Affiliate List diff --git a/internal/handler/public/user/queryUserBalanceLogHandler.go b/internal/handler/public/user/queryUserBalanceLogHandler.go index f83fb9d..3e7e7f3 100644 --- a/internal/handler/public/user/queryUserBalanceLogHandler.go +++ b/internal/handler/public/user/queryUserBalanceLogHandler.go @@ -2,9 +2,9 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Query User Balance Log diff --git a/internal/handler/public/user/queryUserCommissionLogHandler.go b/internal/handler/public/user/queryUserCommissionLogHandler.go index 4ca2a44..733a040 100644 --- a/internal/handler/public/user/queryUserCommissionLogHandler.go +++ b/internal/handler/public/user/queryUserCommissionLogHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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 User Commission Log diff --git a/internal/handler/public/user/queryUserInfoHandler.go b/internal/handler/public/user/queryUserInfoHandler.go index 13c284d..2169f0f 100644 --- a/internal/handler/public/user/queryUserInfoHandler.go +++ b/internal/handler/public/user/queryUserInfoHandler.go @@ -2,9 +2,9 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Query User Info diff --git a/internal/handler/public/user/queryUserSubscribeHandler.go b/internal/handler/public/user/queryUserSubscribeHandler.go index e16aadd..5d7882a 100644 --- a/internal/handler/public/user/queryUserSubscribeHandler.go +++ b/internal/handler/public/user/queryUserSubscribeHandler.go @@ -2,9 +2,9 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Query User Subscribe diff --git a/internal/handler/public/user/resetUserSubscribeTokenHandler.go b/internal/handler/public/user/resetUserSubscribeTokenHandler.go index 2002dc1..545893a 100644 --- a/internal/handler/public/user/resetUserSubscribeTokenHandler.go +++ b/internal/handler/public/user/resetUserSubscribeTokenHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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" ) // Reset User Subscribe Token diff --git a/internal/handler/public/user/unbindDeviceHandler.go b/internal/handler/public/user/unbindDeviceHandler.go new file mode 100644 index 0000000..9429d72 --- /dev/null +++ b/internal/handler/public/user/unbindDeviceHandler.go @@ -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) + } +} diff --git a/internal/handler/public/user/unbindOAuthHandler.go b/internal/handler/public/user/unbindOAuthHandler.go index de9ed68..f4aa124 100644 --- a/internal/handler/public/user/unbindOAuthHandler.go +++ b/internal/handler/public/user/unbindOAuthHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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 OAuth diff --git a/internal/handler/public/user/unbindTelegramHandler.go b/internal/handler/public/user/unbindTelegramHandler.go index 902b58f..b8d9c9e 100644 --- a/internal/handler/public/user/unbindTelegramHandler.go +++ b/internal/handler/public/user/unbindTelegramHandler.go @@ -2,9 +2,9 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/public/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" ) // Unbind Telegram diff --git a/internal/handler/public/user/unsubscribeHandler.go b/internal/handler/public/user/unsubscribeHandler.go index 1ad3bb1..e0792c1 100644 --- a/internal/handler/public/user/unsubscribeHandler.go +++ b/internal/handler/public/user/unsubscribeHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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" ) // Unsubscribe diff --git a/internal/handler/public/user/updateBindEmailHandler.go b/internal/handler/public/user/updateBindEmailHandler.go index a00a5bc..3c06d0d 100644 --- a/internal/handler/public/user/updateBindEmailHandler.go +++ b/internal/handler/public/user/updateBindEmailHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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 Bind Email diff --git a/internal/handler/public/user/updateBindMobileHandler.go b/internal/handler/public/user/updateBindMobileHandler.go index 045d152..333ebe2 100644 --- a/internal/handler/public/user/updateBindMobileHandler.go +++ b/internal/handler/public/user/updateBindMobileHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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 Bind Mobile diff --git a/internal/handler/public/user/updateUserNotifyHandler.go b/internal/handler/public/user/updateUserNotifyHandler.go index 7b38554..8023d86 100644 --- a/internal/handler/public/user/updateUserNotifyHandler.go +++ b/internal/handler/public/user/updateUserNotifyHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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 Notify diff --git a/internal/handler/public/user/updateUserPasswordHandler.go b/internal/handler/public/user/updateUserPasswordHandler.go index 3f23c8a..dd44561 100644 --- a/internal/handler/public/user/updateUserPasswordHandler.go +++ b/internal/handler/public/user/updateUserPasswordHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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 Password diff --git a/internal/handler/public/user/verifyEmailHandler.go b/internal/handler/public/user/verifyEmailHandler.go index d959d70..85bdf5c 100644 --- a/internal/handler/public/user/verifyEmailHandler.go +++ b/internal/handler/public/user/verifyEmailHandler.go @@ -2,10 +2,10 @@ package user import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/public/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "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" ) // Verify Email diff --git a/internal/handler/routes.go b/internal/handler/routes.go index c684245..c6832ec 100644 --- a/internal/handler/routes.go +++ b/internal/handler/routes.go @@ -5,44 +5,37 @@ package handler import ( "github.com/gin-gonic/gin" - adminAds "github.com/perfect-panel/ppanel-server/internal/handler/admin/ads" - adminAnnouncement "github.com/perfect-panel/ppanel-server/internal/handler/admin/announcement" - adminAuthMethod "github.com/perfect-panel/ppanel-server/internal/handler/admin/authMethod" - adminConsole "github.com/perfect-panel/ppanel-server/internal/handler/admin/console" - adminCoupon "github.com/perfect-panel/ppanel-server/internal/handler/admin/coupon" - adminDocument "github.com/perfect-panel/ppanel-server/internal/handler/admin/document" - adminLog "github.com/perfect-panel/ppanel-server/internal/handler/admin/log" - adminOrder "github.com/perfect-panel/ppanel-server/internal/handler/admin/order" - adminPayment "github.com/perfect-panel/ppanel-server/internal/handler/admin/payment" - adminServer "github.com/perfect-panel/ppanel-server/internal/handler/admin/server" - adminSubscribe "github.com/perfect-panel/ppanel-server/internal/handler/admin/subscribe" - adminSystem "github.com/perfect-panel/ppanel-server/internal/handler/admin/system" - adminTicket "github.com/perfect-panel/ppanel-server/internal/handler/admin/ticket" - adminTool "github.com/perfect-panel/ppanel-server/internal/handler/admin/tool" - adminUser "github.com/perfect-panel/ppanel-server/internal/handler/admin/user" - appAnnouncement "github.com/perfect-panel/ppanel-server/internal/handler/app/announcement" - appAuth "github.com/perfect-panel/ppanel-server/internal/handler/app/auth" - appDocument "github.com/perfect-panel/ppanel-server/internal/handler/app/document" - appNode "github.com/perfect-panel/ppanel-server/internal/handler/app/node" - appOrder "github.com/perfect-panel/ppanel-server/internal/handler/app/order" - appPayment "github.com/perfect-panel/ppanel-server/internal/handler/app/payment" - appSubscribe "github.com/perfect-panel/ppanel-server/internal/handler/app/subscribe" - appUser "github.com/perfect-panel/ppanel-server/internal/handler/app/user" - appWs "github.com/perfect-panel/ppanel-server/internal/handler/app/ws" - auth "github.com/perfect-panel/ppanel-server/internal/handler/auth" - authOauth "github.com/perfect-panel/ppanel-server/internal/handler/auth/oauth" - common "github.com/perfect-panel/ppanel-server/internal/handler/common" - publicAnnouncement "github.com/perfect-panel/ppanel-server/internal/handler/public/announcement" - publicDocument "github.com/perfect-panel/ppanel-server/internal/handler/public/document" - publicOrder "github.com/perfect-panel/ppanel-server/internal/handler/public/order" - publicPayment "github.com/perfect-panel/ppanel-server/internal/handler/public/payment" - publicPortal "github.com/perfect-panel/ppanel-server/internal/handler/public/portal" - publicSubscribe "github.com/perfect-panel/ppanel-server/internal/handler/public/subscribe" - publicTicket "github.com/perfect-panel/ppanel-server/internal/handler/public/ticket" - publicUser "github.com/perfect-panel/ppanel-server/internal/handler/public/user" - server "github.com/perfect-panel/ppanel-server/internal/handler/server" - "github.com/perfect-panel/ppanel-server/internal/middleware" - "github.com/perfect-panel/ppanel-server/internal/svc" + adminAds "github.com/perfect-panel/server/internal/handler/admin/ads" + adminAnnouncement "github.com/perfect-panel/server/internal/handler/admin/announcement" + adminApplication "github.com/perfect-panel/server/internal/handler/admin/application" + adminAuthMethod "github.com/perfect-panel/server/internal/handler/admin/authMethod" + adminConsole "github.com/perfect-panel/server/internal/handler/admin/console" + adminCoupon "github.com/perfect-panel/server/internal/handler/admin/coupon" + adminDocument "github.com/perfect-panel/server/internal/handler/admin/document" + adminLog "github.com/perfect-panel/server/internal/handler/admin/log" + adminMarketing "github.com/perfect-panel/server/internal/handler/admin/marketing" + adminOrder "github.com/perfect-panel/server/internal/handler/admin/order" + adminPayment "github.com/perfect-panel/server/internal/handler/admin/payment" + adminServer "github.com/perfect-panel/server/internal/handler/admin/server" + adminSubscribe "github.com/perfect-panel/server/internal/handler/admin/subscribe" + adminSystem "github.com/perfect-panel/server/internal/handler/admin/system" + adminTicket "github.com/perfect-panel/server/internal/handler/admin/ticket" + adminTool "github.com/perfect-panel/server/internal/handler/admin/tool" + adminUser "github.com/perfect-panel/server/internal/handler/admin/user" + auth "github.com/perfect-panel/server/internal/handler/auth" + authOauth "github.com/perfect-panel/server/internal/handler/auth/oauth" + common "github.com/perfect-panel/server/internal/handler/common" + publicAnnouncement "github.com/perfect-panel/server/internal/handler/public/announcement" + publicDocument "github.com/perfect-panel/server/internal/handler/public/document" + publicOrder "github.com/perfect-panel/server/internal/handler/public/order" + publicPayment "github.com/perfect-panel/server/internal/handler/public/payment" + publicPortal "github.com/perfect-panel/server/internal/handler/public/portal" + publicSubscribe "github.com/perfect-panel/server/internal/handler/public/subscribe" + publicTicket "github.com/perfect-panel/server/internal/handler/public/ticket" + publicUser "github.com/perfect-panel/server/internal/handler/public/user" + server "github.com/perfect-panel/server/internal/handler/server" + "github.com/perfect-panel/server/internal/middleware" + "github.com/perfect-panel/server/internal/svc" ) func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { @@ -86,6 +79,26 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { adminAnnouncementGroupRouter.GET("/list", adminAnnouncement.GetAnnouncementListHandler(serverCtx)) } + adminApplicationGroupRouter := router.Group("/v1/admin/application") + adminApplicationGroupRouter.Use(middleware.AuthMiddleware(serverCtx)) + + { + // Create subscribe application + adminApplicationGroupRouter.POST("/", adminApplication.CreateSubscribeApplicationHandler(serverCtx)) + + // Preview Template + adminApplicationGroupRouter.GET("/preview", adminApplication.PreviewSubscribeTemplateHandler(serverCtx)) + + // Update subscribe application + adminApplicationGroupRouter.PUT("/subscribe_application", adminApplication.UpdateSubscribeApplicationHandler(serverCtx)) + + // Delete subscribe application + adminApplicationGroupRouter.DELETE("/subscribe_application", adminApplication.DeleteSubscribeApplicationHandler(serverCtx)) + + // Get subscribe application list + adminApplicationGroupRouter.GET("/subscribe_application_list", adminApplication.GetSubscribeApplicationListHandler(serverCtx)) + } + adminAuthMethodGroupRouter := router.Group("/v1/admin/auth-method") adminAuthMethodGroupRouter.Use(middleware.AuthMiddleware(serverCtx)) @@ -176,8 +189,79 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { adminLogGroupRouter.Use(middleware.AuthMiddleware(serverCtx)) { + // Filter balance log + adminLogGroupRouter.GET("/balance/list", adminLog.FilterBalanceLogHandler(serverCtx)) + + // Filter commission log + adminLogGroupRouter.GET("/commission/list", adminLog.FilterCommissionLogHandler(serverCtx)) + + // Filter email log + adminLogGroupRouter.GET("/email/list", adminLog.FilterEmailLogHandler(serverCtx)) + + // Filter gift log + adminLogGroupRouter.GET("/gift/list", adminLog.FilterGiftLogHandler(serverCtx)) + + // Filter login log + adminLogGroupRouter.GET("/login/list", adminLog.FilterLoginLogHandler(serverCtx)) + // Get message log list adminLogGroupRouter.GET("/message/list", adminLog.GetMessageLogListHandler(serverCtx)) + + // Filter mobile log + adminLogGroupRouter.GET("/mobile/list", adminLog.FilterMobileLogHandler(serverCtx)) + + // Filter register log + adminLogGroupRouter.GET("/register/list", adminLog.FilterRegisterLogHandler(serverCtx)) + + // Filter server traffic log + adminLogGroupRouter.GET("/server/traffic/list", adminLog.FilterServerTrafficLogHandler(serverCtx)) + + // Get log setting + adminLogGroupRouter.GET("/setting", adminLog.GetLogSettingHandler(serverCtx)) + + // Update log setting + adminLogGroupRouter.POST("/setting", adminLog.UpdateLogSettingHandler(serverCtx)) + + // Filter subscribe log + adminLogGroupRouter.GET("/subscribe/list", adminLog.FilterSubscribeLogHandler(serverCtx)) + + // Filter reset subscribe log + adminLogGroupRouter.GET("/subscribe/reset/list", adminLog.FilterResetSubscribeLogHandler(serverCtx)) + + // Filter user subscribe traffic log + adminLogGroupRouter.GET("/subscribe/traffic/list", adminLog.FilterUserSubscribeTrafficLogHandler(serverCtx)) + + // Filter traffic log details + adminLogGroupRouter.GET("/traffic/details", adminLog.FilterTrafficLogDetailsHandler(serverCtx)) + } + + adminMarketingGroupRouter := router.Group("/v1/admin/marketing") + adminMarketingGroupRouter.Use(middleware.AuthMiddleware(serverCtx)) + + { + // Get batch send email task list + adminMarketingGroupRouter.GET("/email/batch/list", adminMarketing.GetBatchSendEmailTaskListHandler(serverCtx)) + + // Get pre-send email count + adminMarketingGroupRouter.POST("/email/batch/pre-send-count", adminMarketing.GetPreSendEmailCountHandler(serverCtx)) + + // Create a batch send email task + adminMarketingGroupRouter.POST("/email/batch/send", adminMarketing.CreateBatchSendEmailTaskHandler(serverCtx)) + + // Get batch send email task status + adminMarketingGroupRouter.POST("/email/batch/status", adminMarketing.GetBatchSendEmailTaskStatusHandler(serverCtx)) + + // Stop a batch send email task + adminMarketingGroupRouter.POST("/email/batch/stop", adminMarketing.StopBatchSendEmailTaskHandler(serverCtx)) + + // Create a quota task + adminMarketingGroupRouter.POST("/quota/create", adminMarketing.CreateQuotaTaskHandler(serverCtx)) + + // Query quota task list + adminMarketingGroupRouter.GET("/quota/list", adminMarketing.QueryQuotaTaskListHandler(serverCtx)) + + // Query quota task pre-count + adminMarketingGroupRouter.POST("/quota/pre-count", adminMarketing.QueryQuotaTaskPreCountHandler(serverCtx)) } adminOrderGroupRouter := router.Group("/v1/admin/order") @@ -218,56 +302,50 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { adminServerGroupRouter.Use(middleware.AuthMiddleware(serverCtx)) { - // Update node - adminServerGroupRouter.PUT("/", adminServer.UpdateNodeHandler(serverCtx)) + // Create Server + adminServerGroupRouter.POST("/create", adminServer.CreateServerHandler(serverCtx)) - // Create node - adminServerGroupRouter.POST("/", adminServer.CreateNodeHandler(serverCtx)) + // Delete Server + adminServerGroupRouter.POST("/delete", adminServer.DeleteServerHandler(serverCtx)) - // Delete node - adminServerGroupRouter.DELETE("/", adminServer.DeleteNodeHandler(serverCtx)) + // Filter Server List + adminServerGroupRouter.GET("/list", adminServer.FilterServerListHandler(serverCtx)) - // Batch delete node - adminServerGroupRouter.DELETE("/batch", adminServer.BatchDeleteNodeHandler(serverCtx)) + // Check if there is any server or node to migrate + adminServerGroupRouter.GET("/migrate/has", adminServer.HasMigrateSeverNodeHandler(serverCtx)) - // Get node detail - adminServerGroupRouter.GET("/detail", adminServer.GetNodeDetailHandler(serverCtx)) + // Migrate server and node data to new database + adminServerGroupRouter.POST("/migrate/run", adminServer.MigrateServerNodeHandler(serverCtx)) - // Create node group - adminServerGroupRouter.POST("/group", adminServer.CreateNodeGroupHandler(serverCtx)) + // Create Node + adminServerGroupRouter.POST("/node/create", adminServer.CreateNodeHandler(serverCtx)) - // Update node group - adminServerGroupRouter.PUT("/group", adminServer.UpdateNodeGroupHandler(serverCtx)) + // Delete Node + adminServerGroupRouter.POST("/node/delete", adminServer.DeleteNodeHandler(serverCtx)) - // Delete node group - adminServerGroupRouter.DELETE("/group", adminServer.DeleteNodeGroupHandler(serverCtx)) + // Filter Node List + adminServerGroupRouter.GET("/node/list", adminServer.FilterNodeListHandler(serverCtx)) - // Batch delete node group - adminServerGroupRouter.DELETE("/group/batch", adminServer.BatchDeleteNodeGroupHandler(serverCtx)) + // Reset node sort + adminServerGroupRouter.POST("/node/sort", adminServer.ResetSortWithNodeHandler(serverCtx)) - // Get node group list - adminServerGroupRouter.GET("/group/list", adminServer.GetNodeGroupListHandler(serverCtx)) + // Toggle Node Status + adminServerGroupRouter.POST("/node/status/toggle", adminServer.ToggleNodeStatusHandler(serverCtx)) - // Get node list - adminServerGroupRouter.GET("/list", adminServer.GetNodeListHandler(serverCtx)) + // Query all node tags + adminServerGroupRouter.GET("/node/tags", adminServer.QueryNodeTagHandler(serverCtx)) - // Create rule group - adminServerGroupRouter.POST("/rule_group", adminServer.CreateRuleGroupHandler(serverCtx)) + // Update Node + adminServerGroupRouter.POST("/node/update", adminServer.UpdateNodeHandler(serverCtx)) - // Update rule group - adminServerGroupRouter.PUT("/rule_group", adminServer.UpdateRuleGroupHandler(serverCtx)) + // Get Server Protocols + adminServerGroupRouter.GET("/protocols", adminServer.GetServerProtocolsHandler(serverCtx)) - // Delete rule group - adminServerGroupRouter.DELETE("/rule_group", adminServer.DeleteRuleGroupHandler(serverCtx)) + // Reset server sort + adminServerGroupRouter.POST("/server/sort", adminServer.ResetSortWithServerHandler(serverCtx)) - // Get rule group list - adminServerGroupRouter.GET("/rule_group_list", adminServer.GetRuleGroupListHandler(serverCtx)) - - // Node sort - adminServerGroupRouter.POST("/sort", adminServer.NodeSortHandler(serverCtx)) - - // Get node tag list - adminServerGroupRouter.GET("/tag/list", adminServer.GetNodeTagListHandler(serverCtx)) + // Update Server + adminServerGroupRouter.POST("/update", adminServer.UpdateServerHandler(serverCtx)) } adminSubscribeGroupRouter := router.Group("/v1/admin/subscribe") @@ -315,33 +393,6 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { adminSystemGroupRouter.Use(middleware.AuthMiddleware(serverCtx)) { - // Get application - adminSystemGroupRouter.GET("/application", adminSystem.GetApplicationHandler(serverCtx)) - - // Update application - adminSystemGroupRouter.PUT("/application", adminSystem.UpdateApplicationHandler(serverCtx)) - - // Create application - adminSystemGroupRouter.POST("/application", adminSystem.CreateApplicationHandler(serverCtx)) - - // Delete application - adminSystemGroupRouter.DELETE("/application", adminSystem.DeleteApplicationHandler(serverCtx)) - - // update application config - adminSystemGroupRouter.PUT("/application_config", adminSystem.UpdateApplicationConfigHandler(serverCtx)) - - // get application config - adminSystemGroupRouter.GET("/application_config", adminSystem.GetApplicationConfigHandler(serverCtx)) - - // Update application version - adminSystemGroupRouter.PUT("/application_version", adminSystem.UpdateApplicationVersionHandler(serverCtx)) - - // Create application version - adminSystemGroupRouter.POST("/application_version", adminSystem.CreateApplicationVersionHandler(serverCtx)) - - // Delete application - adminSystemGroupRouter.DELETE("/application_version", adminSystem.DeleteApplicationVersionHandler(serverCtx)) - // Get Currency Config adminSystemGroupRouter.GET("/currency_config", adminSystem.GetCurrencyConfigHandler(serverCtx)) @@ -363,6 +414,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // Update node config adminSystemGroupRouter.PUT("/node_config", adminSystem.UpdateNodeConfigHandler(serverCtx)) + // PreView Node Multiplier + adminSystemGroupRouter.GET("/node_multiplier/preview", adminSystem.PreViewNodeMultiplierHandler(serverCtx)) + // get Privacy Policy Config adminSystemGroupRouter.GET("/privacy", adminSystem.GetPrivacyPolicyConfigHandler(serverCtx)) @@ -393,9 +447,6 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // Update subscribe config adminSystemGroupRouter.PUT("/subscribe_config", adminSystem.UpdateSubscribeConfigHandler(serverCtx)) - // Get subscribe type - adminSystemGroupRouter.GET("/subscribe_type", adminSystem.GetSubscribeTypeHandler(serverCtx)) - // Get Team of Service Config adminSystemGroupRouter.GET("/tos_config", adminSystem.GetTosConfigHandler(serverCtx)) @@ -441,6 +492,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // Restart System adminToolGroupRouter.GET("/restart", adminTool.RestartSystemHandler(serverCtx)) + + // Get Version + adminToolGroupRouter.GET("/version", adminTool.GetVersionHandler(serverCtx)) } adminUserGroupRouter := router.Group("/v1/admin/user") @@ -516,158 +570,15 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // Get user subcribe logs adminUserGroupRouter.GET("/subscribe/logs", adminUser.GetUserSubscribeLogsHandler(serverCtx)) + // Get user subcribe reset traffic logs + adminUserGroupRouter.GET("/subscribe/reset/logs", adminUser.GetUserSubscribeResetTrafficLogsHandler(serverCtx)) + // Get user subcribe traffic logs adminUserGroupRouter.GET("/subscribe/traffic_logs", adminUser.GetUserSubscribeTrafficLogsHandler(serverCtx)) } - appAnnouncementGroupRouter := router.Group("/v1/app/announcement") - appAnnouncementGroupRouter.Use(middleware.AppMiddleware(serverCtx), middleware.AuthMiddleware(serverCtx)) - - { - // Query announcement - appAnnouncementGroupRouter.GET("/list", appAnnouncement.QueryAnnouncementHandler(serverCtx)) - } - - appAuthGroupRouter := router.Group("/v1/app/auth") - appAuthGroupRouter.Use(middleware.AppMiddleware(serverCtx)) - - { - // Check Account - appAuthGroupRouter.POST("/check", appAuth.CheckHandler(serverCtx)) - - // GetAppConfig - appAuthGroupRouter.POST("/config", appAuth.GetAppConfigHandler(serverCtx)) - - // Login - appAuthGroupRouter.POST("/login", appAuth.LoginHandler(serverCtx)) - - // Register - appAuthGroupRouter.POST("/register", appAuth.RegisterHandler(serverCtx)) - - // Reset Password - appAuthGroupRouter.POST("/reset_password", appAuth.ResetPasswordHandler(serverCtx)) - } - - appDocumentGroupRouter := router.Group("/v1/app/document") - appDocumentGroupRouter.Use(middleware.AppMiddleware(serverCtx), middleware.AuthMiddleware(serverCtx)) - - { - // Get document detail - appDocumentGroupRouter.GET("/detail", appDocument.QueryDocumentDetailHandler(serverCtx)) - - // Get document list - appDocumentGroupRouter.GET("/list", appDocument.QueryDocumentListHandler(serverCtx)) - } - - appNodeGroupRouter := router.Group("/v1/app/node") - appNodeGroupRouter.Use(middleware.AppMiddleware(serverCtx), middleware.AuthMiddleware(serverCtx)) - - { - // Get Node list - appNodeGroupRouter.GET("/list", appNode.GetNodeListHandler(serverCtx)) - - // Get rule group list - appNodeGroupRouter.GET("/rule_group_list", appNode.GetRuleGroupListHandler(serverCtx)) - } - - appOrderGroupRouter := router.Group("/v1/app/order") - appOrderGroupRouter.Use(middleware.AppMiddleware(serverCtx), middleware.AuthMiddleware(serverCtx)) - - { - // Checkout order - appOrderGroupRouter.POST("/checkout", appOrder.CheckoutOrderHandler(serverCtx)) - - // Close order - appOrderGroupRouter.POST("/close", appOrder.CloseOrderHandler(serverCtx)) - - // Get order - appOrderGroupRouter.GET("/detail", appOrder.QueryOrderDetailHandler(serverCtx)) - - // Get order list - appOrderGroupRouter.GET("/list", appOrder.QueryOrderListHandler(serverCtx)) - - // Pre create order - appOrderGroupRouter.POST("/pre", appOrder.PreCreateOrderHandler(serverCtx)) - - // purchase Subscription - appOrderGroupRouter.POST("/purchase", appOrder.PurchaseHandler(serverCtx)) - - // Recharge - appOrderGroupRouter.POST("/recharge", appOrder.RechargeHandler(serverCtx)) - - // Renewal Subscription - appOrderGroupRouter.POST("/renewal", appOrder.RenewalHandler(serverCtx)) - - // Reset traffic - appOrderGroupRouter.POST("/reset", appOrder.ResetTrafficHandler(serverCtx)) - } - - appPaymentGroupRouter := router.Group("/v1/app/payment") - appPaymentGroupRouter.Use(middleware.AppMiddleware(serverCtx), middleware.AuthMiddleware(serverCtx)) - - { - // Get available payment methods - appPaymentGroupRouter.GET("/methods", appPayment.GetAvailablePaymentMethodsHandler(serverCtx)) - } - - appSubscribeGroupRouter := router.Group("/v1/app/subscribe") - appSubscribeGroupRouter.Use(middleware.AppMiddleware(serverCtx), middleware.AuthMiddleware(serverCtx)) - - { - // Get application config - appSubscribeGroupRouter.GET("/application/config", appSubscribe.QueryApplicationConfigHandler(serverCtx)) - - // Get subscribe group list - appSubscribeGroupRouter.GET("/group/list", appSubscribe.QuerySubscribeGroupListHandler(serverCtx)) - - // Get subscribe list - appSubscribeGroupRouter.GET("/list", appSubscribe.QuerySubscribeListHandler(serverCtx)) - - // Reset user subscription period - appSubscribeGroupRouter.POST("/reset/period", appSubscribe.ResetUserSubscribePeriodHandler(serverCtx)) - - // Get Already subscribed to package - appSubscribeGroupRouter.GET("/user/already_subscribe", appSubscribe.QueryUserAlreadySubscribeHandler(serverCtx)) - - // Get Available subscriptions for users - appSubscribeGroupRouter.GET("/user/available_subscribe", appSubscribe.QueryUserAvailableUserSubscribeHandler(serverCtx)) - } - - appUserGroupRouter := router.Group("/v1/app/user") - appUserGroupRouter.Use(middleware.AppMiddleware(serverCtx), middleware.AuthMiddleware(serverCtx)) - - { - // Delete Account - appUserGroupRouter.DELETE("/account", appUser.DeleteAccountHandler(serverCtx)) - - // Query User Affiliate Count - appUserGroupRouter.GET("/affiliate/count", appUser.QueryUserAffiliateHandler(serverCtx)) - - // Query User Affiliate List - appUserGroupRouter.GET("/affiliate/list", appUser.QueryUserAffiliateListHandler(serverCtx)) - - // query user info - appUserGroupRouter.GET("/info", appUser.QueryUserInfoHandler(serverCtx)) - - // Get user online time total - appUserGroupRouter.GET("/online_time/statistics", appUser.GetUserOnlineTimeStatisticsHandler(serverCtx)) - - // Update Password - appUserGroupRouter.PUT("/password", appUser.UpdatePasswordHandler(serverCtx)) - - // Get user subcribe traffic logs - appUserGroupRouter.GET("/subscribe/traffic_logs", appUser.GetUserSubscribeTrafficLogsHandler(serverCtx)) - } - - appWsGroupRouter := router.Group("/v1/app/ws") - appWsGroupRouter.Use(middleware.AuthMiddleware(serverCtx)) - - { - // App heartbeat - appWsGroupRouter.GET("/:userid/:identifier", appWs.AppWsHandler(serverCtx)) - } - authGroupRouter := router.Group("/v1/auth") + authGroupRouter.Use(middleware.DeviceMiddleware(serverCtx)) { // Check user is exist @@ -679,6 +590,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // User login authGroupRouter.POST("/login", auth.UserLoginHandler(serverCtx)) + // Device Login + authGroupRouter.POST("/login/device", auth.DeviceLoginHandler(serverCtx)) + // User Telephone login authGroupRouter.POST("/login/telephone", auth.TelephoneLoginHandler(serverCtx)) @@ -709,17 +623,18 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { } commonGroupRouter := router.Group("/v1/common") + commonGroupRouter.Use(middleware.DeviceMiddleware(serverCtx)) { // Get Ads commonGroupRouter.GET("/ads", common.GetAdsHandler(serverCtx)) - // Get Tos Content - commonGroupRouter.GET("/application", common.GetApplicationHandler(serverCtx)) - // Check verification code commonGroupRouter.POST("/check_verification_code", common.CheckVerificationCodeHandler(serverCtx)) + // Get Client + commonGroupRouter.GET("/client", common.GetClientHandler(serverCtx)) + // Get verification code commonGroupRouter.POST("/send_code", common.SendEmailCodeHandler(serverCtx)) @@ -740,7 +655,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { } publicAnnouncementGroupRouter := router.Group("/v1/public/announcement") - publicAnnouncementGroupRouter.Use(middleware.AuthMiddleware(serverCtx)) + publicAnnouncementGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx)) { // Query announcement @@ -748,7 +663,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { } publicDocumentGroupRouter := router.Group("/v1/public/document") - publicDocumentGroupRouter.Use(middleware.AuthMiddleware(serverCtx)) + publicDocumentGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx)) { // Get document detail @@ -759,7 +674,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { } publicOrderGroupRouter := router.Group("/v1/public/order") - publicOrderGroupRouter.Use(middleware.AuthMiddleware(serverCtx)) + publicOrderGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx)) { // Close order @@ -788,7 +703,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { } publicPaymentGroupRouter := router.Group("/v1/public/payment") - publicPaymentGroupRouter.Use(middleware.AuthMiddleware(serverCtx)) + publicPaymentGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx)) { // Get available payment methods @@ -796,6 +711,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { } publicPortalGroupRouter := router.Group("/v1/public/portal") + publicPortalGroupRouter.Use(middleware.DeviceMiddleware(serverCtx)) { // Purchase Checkout @@ -818,21 +734,15 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { } publicSubscribeGroupRouter := router.Group("/v1/public/subscribe") - publicSubscribeGroupRouter.Use(middleware.AuthMiddleware(serverCtx)) + publicSubscribeGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx)) { - // Get application config - publicSubscribeGroupRouter.GET("/application/config", publicSubscribe.QueryApplicationConfigHandler(serverCtx)) - - // Get subscribe group list - publicSubscribeGroupRouter.GET("/group/list", publicSubscribe.QuerySubscribeGroupListHandler(serverCtx)) - // Get subscribe list publicSubscribeGroupRouter.GET("/list", publicSubscribe.QuerySubscribeListHandler(serverCtx)) } publicTicketGroupRouter := router.Group("/v1/public/ticket") - publicTicketGroupRouter.Use(middleware.AuthMiddleware(serverCtx)) + publicTicketGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx)) { // Update ticket status @@ -852,7 +762,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { } publicUserGroupRouter := router.Group("/v1/public/user") - publicUserGroupRouter.Use(middleware.AuthMiddleware(serverCtx)) + publicUserGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx)) { // Query User Affiliate Count @@ -882,6 +792,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // Query User Commission Log publicUserGroupRouter.GET("/commission_log", publicUser.QueryUserCommissionLogHandler(serverCtx)) + // Get Device List + publicUserGroupRouter.GET("/devices", publicUser.GetDeviceListHandler(serverCtx)) + // Query User Info publicUserGroupRouter.GET("/info", publicUser.QueryUserInfoHandler(serverCtx)) @@ -906,6 +819,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // Reset User Subscribe Token publicUserGroupRouter.PUT("/subscribe_token", publicUser.ResetUserSubscribeTokenHandler(serverCtx)) + // Unbind Device + publicUserGroupRouter.PUT("/unbind_device", publicUser.UnbindDeviceHandler(serverCtx)) + // Unbind OAuth publicUserGroupRouter.POST("/unbind_oauth", publicUser.UnbindOAuthHandler(serverCtx)) @@ -941,4 +857,11 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // Get user list serverGroupRouter.GET("/user", server.GetServerUserListHandler(serverCtx)) } + + serverV2GroupRouter := router.Group("/v2/server") + + { + // Get Server Protocol Config + serverV2GroupRouter.GET("/:server_id", server.QueryServerProtocolConfigHandler(serverCtx)) + } } diff --git a/internal/handler/server/getServerConfigHandler.go b/internal/handler/server/getServerConfigHandler.go index e849633..5428340 100644 --- a/internal/handler/server/getServerConfigHandler.go +++ b/internal/handler/server/getServerConfigHandler.go @@ -2,11 +2,11 @@ package server import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/logic/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) diff --git a/internal/handler/server/getServerUserListHandler.go b/internal/handler/server/getServerUserListHandler.go index cfa143c..eeb5e24 100644 --- a/internal/handler/server/getServerUserListHandler.go +++ b/internal/handler/server/getServerUserListHandler.go @@ -2,11 +2,11 @@ package server import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/logic/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) diff --git a/internal/handler/server/pushOnlineUsersHandler.go b/internal/handler/server/pushOnlineUsersHandler.go index 0bf8258..bf09594 100644 --- a/internal/handler/server/pushOnlineUsersHandler.go +++ b/internal/handler/server/pushOnlineUsersHandler.go @@ -2,10 +2,10 @@ package server import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Push online users diff --git a/internal/handler/server/queryServerProtocolConfigHandler.go b/internal/handler/server/queryServerProtocolConfigHandler.go new file mode 100644 index 0000000..5d382a6 --- /dev/null +++ b/internal/handler/server/queryServerProtocolConfigHandler.go @@ -0,0 +1,43 @@ +package server + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/server" + "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/result" +) + +// QueryServerProtocolConfigHandler Get Server Protocol Config +func QueryServerProtocolConfigHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.QueryServerConfigRequest + + serverID, err := strconv.ParseInt(c.Param("server_id"), 10, 64) + if err != nil { + logger.Debugf("[QueryServerProtocolConfigHandler] - strconv.ParseInt(server_id) error: %v, Param: %s", err, c.Param("server_id")) + c.String(http.StatusBadRequest, "Invalid Params") + c.Abort() + return + } + req.ServerID = serverID + + if err = c.ShouldBindQuery(&req); err != nil { + logger.Debugf("[QueryServerProtocolConfigHandler] - ShouldBindQuery error: %v, Query: %v", err, c.Request.URL.Query()) + c.String(http.StatusBadRequest, "Invalid Params") + c.Abort() + return + } + + fmt.Printf("[QueryServerProtocolConfigHandler] - ShouldBindQuery request: %+v\n", req) + + l := server.NewQueryServerProtocolConfigLogic(c.Request.Context(), svcCtx) + resp, err := l.QueryServerProtocolConfig(&req) + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/server/serverPushStatusHandler.go b/internal/handler/server/serverPushStatusHandler.go index b690b21..c6e58ae 100644 --- a/internal/handler/server/serverPushStatusHandler.go +++ b/internal/handler/server/serverPushStatusHandler.go @@ -2,10 +2,10 @@ package server import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // Push server status diff --git a/internal/handler/server/serverPushUserTrafficHandler.go b/internal/handler/server/serverPushUserTrafficHandler.go index 156f29e..c115b4e 100644 --- a/internal/handler/server/serverPushUserTrafficHandler.go +++ b/internal/handler/server/serverPushUserTrafficHandler.go @@ -2,10 +2,10 @@ package server import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/result" + "github.com/perfect-panel/server/internal/logic/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" ) // ServerPushUserTrafficHandler Push user Traffic diff --git a/internal/handler/subscribe.go b/internal/handler/subscribe.go index 391fe38..bf72a19 100644 --- a/internal/handler/subscribe.go +++ b/internal/handler/subscribe.go @@ -1,10 +1,15 @@ package handler import ( + "net/http" + "strings" + "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" + "github.com/perfect-panel/server/internal/logic/subscribe" + "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" ) func SubscribeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { @@ -15,11 +20,50 @@ func SubscribeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { } else { req.Token = c.Query("token") } + ua := c.GetHeader("User-Agent") req.UA = c.Request.Header.Get("User-Agent") req.Flag = c.Query("flag") + + if svcCtx.Config.Subscribe.UserAgentLimit { + if ua == "" { + c.String(http.StatusForbidden, "Access denied") + c.Abort() + return + } + clientUserAgents := tool.RemoveDuplicateElements(strings.Split(svcCtx.Config.Subscribe.UserAgentList, "\n")...) + + // query client list + clients, err := svcCtx.ClientModel.List(c.Request.Context()) + if err != nil { + logger.Errorw("[PanDomainMiddleware] Query client list failed", logger.Field("error", err.Error())) + } + for _, item := range clients { + u := strings.ToLower(item.UserAgent) + u = strings.Trim(u, " ") + clientUserAgents = append(clientUserAgents, u) + } + + var allow = false + for _, keyword := range clientUserAgents { + keyword = strings.Trim(keyword, " ") + if keyword == "" { + continue + } + if strings.Contains(strings.ToLower(ua), strings.ToLower(keyword)) { + allow = true + } + } + if !allow { + c.String(http.StatusForbidden, "Access denied") + c.Abort() + return + } + } + l := subscribe.NewSubscribeLogic(c, svcCtx) - resp, err := l.Generate(&req) + resp, err := l.Handler(&req) if err != nil { + c.String(http.StatusInternalServerError, "Internal Server") return } c.Header("subscription-userinfo", resp.Header) @@ -30,7 +74,7 @@ func SubscribeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { func RegisterSubscribeHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { path := serverCtx.Config.Subscribe.SubscribePath if path == "" { - path = "/api/subscribe" + path = "/v1/subscribe/config" } router.GET(path, SubscribeHandler(serverCtx)) } diff --git a/internal/handler/telegram.go b/internal/handler/telegram.go index 28fb83c..994f7ad 100644 --- a/internal/handler/telegram.go +++ b/internal/handler/telegram.go @@ -3,11 +3,11 @@ package handler import ( "github.com/gin-gonic/gin" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" - "github.com/perfect-panel/ppanel-server/internal/logic/telegram" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/result" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/internal/logic/telegram" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/result" + "github.com/perfect-panel/server/pkg/tool" ) func RegisterTelegramHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { diff --git a/internal/logic/admin/ads/createAdsLogic.go b/internal/logic/admin/ads/createAdsLogic.go index 1ee3435..c4b0df3 100644 --- a/internal/logic/admin/ads/createAdsLogic.go +++ b/internal/logic/admin/ads/createAdsLogic.go @@ -4,11 +4,11 @@ import ( "context" "time" - "github.com/perfect-panel/ppanel-server/internal/model/ads" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/ads" + "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" ) diff --git a/internal/logic/admin/ads/deleteAdsLogic.go b/internal/logic/admin/ads/deleteAdsLogic.go index a9588c8..5a2f461 100644 --- a/internal/logic/admin/ads/deleteAdsLogic.go +++ b/internal/logic/admin/ads/deleteAdsLogic.go @@ -3,10 +3,10 @@ package ads import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/ads/getAdsDetailLogic.go b/internal/logic/admin/ads/getAdsDetailLogic.go index 2897ad3..ff8331f 100644 --- a/internal/logic/admin/ads/getAdsDetailLogic.go +++ b/internal/logic/admin/ads/getAdsDetailLogic.go @@ -3,11 +3,11 @@ package ads import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/ads/getAdsListLogic.go b/internal/logic/admin/ads/getAdsListLogic.go index 5c531fe..c23e519 100644 --- a/internal/logic/admin/ads/getAdsListLogic.go +++ b/internal/logic/admin/ads/getAdsListLogic.go @@ -3,12 +3,12 @@ package ads import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/ads" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/ads" + "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" ) diff --git a/internal/logic/admin/ads/updateAdsLogic.go b/internal/logic/admin/ads/updateAdsLogic.go index bf99931..9939624 100644 --- a/internal/logic/admin/ads/updateAdsLogic.go +++ b/internal/logic/admin/ads/updateAdsLogic.go @@ -4,11 +4,11 @@ import ( "context" "time" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/announcement/createAnnouncementLogic.go b/internal/logic/admin/announcement/createAnnouncementLogic.go index d0ada9f..a1fe3b7 100644 --- a/internal/logic/admin/announcement/createAnnouncementLogic.go +++ b/internal/logic/admin/announcement/createAnnouncementLogic.go @@ -3,11 +3,11 @@ package announcement import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/announcement" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/announcement" + "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" ) diff --git a/internal/logic/admin/announcement/deleteAnnouncementLogic.go b/internal/logic/admin/announcement/deleteAnnouncementLogic.go index edee3ac..62ebded 100644 --- a/internal/logic/admin/announcement/deleteAnnouncementLogic.go +++ b/internal/logic/admin/announcement/deleteAnnouncementLogic.go @@ -3,10 +3,10 @@ package announcement import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/announcement/getAnnouncementListLogic.go b/internal/logic/admin/announcement/getAnnouncementListLogic.go index 1b15b87..92a2147 100644 --- a/internal/logic/admin/announcement/getAnnouncementListLogic.go +++ b/internal/logic/admin/announcement/getAnnouncementListLogic.go @@ -3,14 +3,14 @@ package announcement import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/announcement" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/announcement" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type GetAnnouncementListLogic struct { diff --git a/internal/logic/admin/announcement/getAnnouncementLogic.go b/internal/logic/admin/announcement/getAnnouncementLogic.go index 9139d03..29ca1eb 100644 --- a/internal/logic/admin/announcement/getAnnouncementLogic.go +++ b/internal/logic/admin/announcement/getAnnouncementLogic.go @@ -3,11 +3,11 @@ package announcement import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/announcement/updateAnnouncementLogic.go b/internal/logic/admin/announcement/updateAnnouncementLogic.go index 50d217b..cf10d07 100644 --- a/internal/logic/admin/announcement/updateAnnouncementLogic.go +++ b/internal/logic/admin/announcement/updateAnnouncementLogic.go @@ -3,10 +3,10 @@ package announcement import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/application/createSubscribeApplicationLogic.go b/internal/logic/admin/application/createSubscribeApplicationLogic.go new file mode 100644 index 0000000..4fca252 --- /dev/null +++ b/internal/logic/admin/application/createSubscribeApplicationLogic.go @@ -0,0 +1,61 @@ +package application + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/client" + "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 CreateSubscribeApplicationLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewCreateSubscribeApplicationLogic Create subscribe application +func NewCreateSubscribeApplicationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateSubscribeApplicationLogic { + return &CreateSubscribeApplicationLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *CreateSubscribeApplicationLogic) CreateSubscribeApplication(req *types.CreateSubscribeApplicationRequest) (resp *types.SubscribeApplication, err error) { + var link client.DownloadLink + tool.DeepCopy(&link, req.DownloadLink) + linkData, err := link.Marshal() + if err != nil { + l.Errorf("Failed to marshal download link: %v", err) + return nil, errors.Wrap(xerr.NewErrCode(xerr.ERROR), " Failed to marshal download link") + } + data := &client.SubscribeApplication{ + Name: req.Name, + Icon: req.Icon, + Description: req.Description, + Scheme: req.Scheme, + UserAgent: req.UserAgent, + IsDefault: req.IsDefault, + SubscribeTemplate: req.SubscribeTemplate, + OutputFormat: req.OutputFormat, + DownloadLink: string(linkData), + } + + err = l.svcCtx.ClientModel.Insert(l.ctx, data) + if err != nil { + l.Errorf("Failed to create subscribe application: %v", err) + return nil, errors.Wrap(xerr.NewErrCode(xerr.DatabaseInsertError), "Failed to create subscribe application") + } + + resp = &types.SubscribeApplication{} + tool.DeepCopy(resp, data) + resp.DownloadLink = req.DownloadLink + + return +} diff --git a/internal/logic/admin/application/deleteSubscribeApplicationLogic.go b/internal/logic/admin/application/deleteSubscribeApplicationLogic.go new file mode 100644 index 0000000..57cdd70 --- /dev/null +++ b/internal/logic/admin/application/deleteSubscribeApplicationLogic.go @@ -0,0 +1,35 @@ +package application + +import ( + "context" + + "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 DeleteSubscribeApplicationLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewDeleteSubscribeApplicationLogic Delete subscribe application +func NewDeleteSubscribeApplicationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteSubscribeApplicationLogic { + return &DeleteSubscribeApplicationLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *DeleteSubscribeApplicationLogic) DeleteSubscribeApplication(req *types.DeleteSubscribeApplicationRequest) error { + err := l.svcCtx.ClientModel.Delete(l.ctx, req.Id) + if err != nil { + l.Errorf("Failed to delete subscribe application with ID %d: %v", req.Id, err) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), err.Error()) + } + return nil +} diff --git a/internal/logic/admin/application/getSubscribeApplicationListLogic.go b/internal/logic/admin/application/getSubscribeApplicationListLogic.go new file mode 100644 index 0000000..383b2b6 --- /dev/null +++ b/internal/logic/admin/application/getSubscribeApplicationListLogic.go @@ -0,0 +1,61 @@ +package application + +import ( + "context" + "encoding/json" + + "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 GetSubscribeApplicationListLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewGetSubscribeApplicationListLogic Get subscribe application list +func NewGetSubscribeApplicationListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetSubscribeApplicationListLogic { + return &GetSubscribeApplicationListLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetSubscribeApplicationListLogic) GetSubscribeApplicationList(req *types.GetSubscribeApplicationListRequest) (resp *types.GetSubscribeApplicationListResponse, err error) { + data, err := l.svcCtx.ClientModel.List(l.ctx) + if err != nil { + l.Errorf("Failed to get subscribe application list: %v", err) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Failed to get subscribe application list") + } + var list []types.SubscribeApplication + for _, item := range data { + var temp types.DownloadLink + if item.DownloadLink != "" { + _ = json.Unmarshal([]byte(item.DownloadLink), &temp) + } + list = append(list, types.SubscribeApplication{ + Id: item.Id, + Name: item.Name, + Description: item.Description, + Icon: item.Icon, + Scheme: item.Scheme, + UserAgent: item.UserAgent, + IsDefault: item.IsDefault, + SubscribeTemplate: item.SubscribeTemplate, + OutputFormat: item.OutputFormat, + DownloadLink: temp, + CreatedAt: item.CreatedAt.UnixMilli(), + UpdatedAt: item.UpdatedAt.UnixMilli(), + }) + } + resp = &types.GetSubscribeApplicationListResponse{ + Total: int64(len(list)), + List: list, + } + return +} diff --git a/internal/logic/admin/application/previewSubscribeTemplateLogic.go b/internal/logic/admin/application/previewSubscribeTemplateLogic.go new file mode 100644 index 0000000..8ca72e0 --- /dev/null +++ b/internal/logic/admin/application/previewSubscribeTemplateLogic.go @@ -0,0 +1,76 @@ +package application + +import ( + "context" + "time" + + "github.com/perfect-panel/server/adapter" + "github.com/perfect-panel/server/internal/model/node" + "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 PreviewSubscribeTemplateLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// Preview Template +func NewPreviewSubscribeTemplateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PreviewSubscribeTemplateLogic { + return &PreviewSubscribeTemplateLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *PreviewSubscribeTemplateLogic) PreviewSubscribeTemplate(req *types.PreviewSubscribeTemplateRequest) (resp *types.PreviewSubscribeTemplateResponse, err error) { + enable := true + _, servers, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{ + Page: 1, + Size: 1000, + Preload: true, + Enabled: &enable, + }) + if err != nil { + l.Errorf("[PreviewSubscribeTemplateLogic] FindAllServer error: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindAllServer error: %v", err.Error()) + } + + data, err := l.svcCtx.ClientModel.FindOne(l.ctx, req.Id) + if err != nil { + l.Errorf("[PreviewSubscribeTemplateLogic] FindOne error: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindOneClient error: %v", err.Error()) + } + + sub := adapter.NewAdapter(data.SubscribeTemplate, adapter.WithServers(servers), + adapter.WithSiteName("PerfectPanel"), + adapter.WithSubscribeName("Test Subscribe"), + adapter.WithOutputFormat(data.OutputFormat), + adapter.WithUserInfo(adapter.User{ + Password: "test-password", + ExpiredAt: time.Now().AddDate(1, 0, 0), + Download: 0, + Upload: 0, + Traffic: 1000, + SubscribeURL: "https://example.com/subscribe", + })) + // Get client config + a, err := sub.Client() + if err != nil { + l.Errorf("[PreviewSubscribeTemplateLogic] Client error: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrMsg(err.Error()), "Client error: %v", err.Error()) + } + bytes, err := a.Build() + if err != nil { + l.Errorf("[PreviewSubscribeTemplateLogic] Build error: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrMsg(err.Error()), "Build error: %v", err.Error()) + } + return &types.PreviewSubscribeTemplateResponse{ + Template: string(bytes), + }, nil +} diff --git a/internal/logic/admin/application/updateSubscribeApplicationLogic.go b/internal/logic/admin/application/updateSubscribeApplicationLogic.go new file mode 100644 index 0000000..b209768 --- /dev/null +++ b/internal/logic/admin/application/updateSubscribeApplicationLogic.go @@ -0,0 +1,62 @@ +package application + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/client" + "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 UpdateSubscribeApplicationLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewUpdateSubscribeApplicationLogic Update subscribe application +func NewUpdateSubscribeApplicationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateSubscribeApplicationLogic { + return &UpdateSubscribeApplicationLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *UpdateSubscribeApplicationLogic) UpdateSubscribeApplication(req *types.UpdateSubscribeApplicationRequest) (resp *types.SubscribeApplication, err error) { + data, err := l.svcCtx.ClientModel.FindOne(l.ctx, req.Id) + if err != nil { + l.Errorf("Failed to find subscribe application with ID %d: %v", req.Id, err) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Failed to find subscribe application with ID %d", req.Id) + } + var link client.DownloadLink + tool.DeepCopy(&link, req.DownloadLink) + linkData, err := link.Marshal() + if err != nil { + l.Errorf("Failed to marshal download link: %v", err) + return nil, errors.Wrap(xerr.NewErrCode(xerr.ERROR), " Failed to marshal download link") + } + + data.Name = req.Name + data.Icon = req.Icon + data.Description = req.Description + data.Scheme = req.Scheme + data.UserAgent = req.UserAgent + data.IsDefault = req.IsDefault + data.SubscribeTemplate = req.SubscribeTemplate + data.OutputFormat = req.OutputFormat + data.DownloadLink = string(linkData) + err = l.svcCtx.ClientModel.Update(l.ctx, data) + if err != nil { + l.Errorf("Failed to update subscribe application with ID %d: %v", req.Id, err) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "Failed to update subscribe application with ID %d", req.Id) + } + resp = &types.SubscribeApplication{} + tool.DeepCopy(&resp, data) + resp.DownloadLink = req.DownloadLink + return +} diff --git a/internal/logic/admin/authMethod/getAuthMethodConfigLogic.go b/internal/logic/admin/authMethod/getAuthMethodConfigLogic.go index c0ac121..febee66 100644 --- a/internal/logic/admin/authMethod/getAuthMethodConfigLogic.go +++ b/internal/logic/admin/authMethod/getAuthMethodConfigLogic.go @@ -4,11 +4,11 @@ import ( "context" "encoding/json" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) @@ -18,7 +18,7 @@ type GetAuthMethodConfigLogic struct { svcCtx *svc.ServiceContext } -// Get auth method config +// NewGetAuthMethodConfigLogic Get auth method config func NewGetAuthMethodConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAuthMethodConfigLogic { return &GetAuthMethodConfigLogic{ Logger: logger.WithContext(ctx), diff --git a/internal/logic/admin/authMethod/getAuthMethodListLogic.go b/internal/logic/admin/authMethod/getAuthMethodListLogic.go index 5265064..07883d9 100644 --- a/internal/logic/admin/authMethod/getAuthMethodListLogic.go +++ b/internal/logic/admin/authMethod/getAuthMethodListLogic.go @@ -4,11 +4,11 @@ import ( "context" "encoding/json" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/authMethod/getEmailPlatformLogic.go b/internal/logic/admin/authMethod/getEmailPlatformLogic.go index 6e6a607..cf9aa72 100644 --- a/internal/logic/admin/authMethod/getEmailPlatformLogic.go +++ b/internal/logic/admin/authMethod/getEmailPlatformLogic.go @@ -3,11 +3,11 @@ package authMethod import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/email" + "github.com/perfect-panel/server/pkg/email" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type GetEmailPlatformLogic struct { diff --git a/internal/logic/admin/authMethod/getSmsPlatformLogic.go b/internal/logic/admin/authMethod/getSmsPlatformLogic.go index a612a50..7b5b15d 100644 --- a/internal/logic/admin/authMethod/getSmsPlatformLogic.go +++ b/internal/logic/admin/authMethod/getSmsPlatformLogic.go @@ -3,11 +3,11 @@ package authMethod import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/sms" + "github.com/perfect-panel/server/pkg/sms" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type GetSmsPlatformLogic struct { diff --git a/internal/logic/admin/authMethod/testEmailSendLogic.go b/internal/logic/admin/authMethod/testEmailSendLogic.go index 8c071ba..3a5b636 100644 --- a/internal/logic/admin/authMethod/testEmailSendLogic.go +++ b/internal/logic/admin/authMethod/testEmailSendLogic.go @@ -4,11 +4,11 @@ import ( "context" "fmt" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/email" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/email" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) diff --git a/internal/logic/admin/authMethod/testSmsSendLogic.go b/internal/logic/admin/authMethod/testSmsSendLogic.go index 75e9f4d..ea0cdb2 100644 --- a/internal/logic/admin/authMethod/testSmsSendLogic.go +++ b/internal/logic/admin/authMethod/testSmsSendLogic.go @@ -4,11 +4,11 @@ import ( "context" "fmt" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/sms" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/sms" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) diff --git a/internal/logic/admin/authMethod/updateAuthMethodConfigLogic.go b/internal/logic/admin/authMethod/updateAuthMethodConfigLogic.go index 57cb46b..d61e38f 100644 --- a/internal/logic/admin/authMethod/updateAuthMethodConfigLogic.go +++ b/internal/logic/admin/authMethod/updateAuthMethodConfigLogic.go @@ -4,15 +4,15 @@ import ( "context" "encoding/json" - "github.com/perfect-panel/ppanel-server/initialize" - "github.com/perfect-panel/ppanel-server/internal/model/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/email" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/sms" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/initialize" + "github.com/perfect-panel/server/internal/model/auth" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/email" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/sms" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) @@ -40,34 +40,32 @@ func (l *UpdateAuthMethodConfigLogic) UpdateAuthMethodConfig(req *types.UpdateAu tool.DeepCopy(method, req) if req.Config != nil { - if value, ok := req.Config.(map[string]interface{}); ok { - if req.Method == "email" && value["verify_email_template"] == "" { - value["verify_email_template"] = email.DefaultEmailVerifyTemplate - } - if req.Method == "email" && value["expiration_email_template"] == "" { - value["expiration_email_template"] = email.DefaultExpirationEmailTemplate - } - if req.Method == "email" && value["maintenance_email_template"] == "" { - value["maintenance_email_template"] = email.DefaultMaintenanceEmailTemplate - } - if req.Method == "email" && value["traffic_exceed_email_template"] == "" { - value["traffic_exceed_email_template"] = email.DefaultTrafficExceedEmailTemplate - } - - if value["platform_config"] != nil { - platformConfig, err := validatePlatformConfig(value["platform"].(string), value["platform_config"].(map[string]interface{})) - if err != nil { - l.Errorw("validate platform config failed", logger.Field("config", req.Config), logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "validate platform config failed: %v", err.Error()) - } - req.Config.(map[string]interface{})["platform_config"] = platformConfig - } + _, exist := req.Config.(map[string]interface{}) + if !exist { + req.Config = initializePlatformConfig(req.Method).(string) } + if req.Method == "email" { + configs, _ := json.Marshal(req.Config) + emailConfig := new(auth.EmailAuthConfig) + emailConfig.Unmarshal(string(configs)) + req.Config = emailConfig + } + + if req.Method == "mobile" { + configs, _ := json.Marshal(req.Config) + mobileConfig := new(auth.MobileAuthConfig) + mobileConfig.Unmarshal(string(configs)) + req.Config = mobileConfig + } + bytes, err := json.Marshal(req.Config) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "marshal config failed: %v", err.Error()) } method.Config = string(bytes) + } else { + // initialize platform config + method.Config = initializePlatformConfig(req.Method).(string) } err = l.svcCtx.AuthModel.Update(l.ctx, method) if err != nil { @@ -94,6 +92,9 @@ func (l *UpdateAuthMethodConfigLogic) UpdateGlobal(method string) { if method == "mobile" { initialize.Mobile(l.svcCtx) } + if method == "device" { + initialize.Device(l.svcCtx) + } } func validatePlatformConfig(platform string, cfg map[string]interface{}) (interface{}, error) { @@ -124,3 +125,26 @@ func validatePlatformConfig(platform string, cfg map[string]interface{}) (interf } return config, nil } + +func initializePlatformConfig(platform string) interface{} { + var result interface{} + switch platform { + case "email": + result = new(auth.EmailAuthConfig).Marshal() + case "mobile": + result = new(auth.MobileAuthConfig).Marshal() + case "apple": + result = new(auth.AppleAuthConfig).Marshal() + case "google": + result = new(auth.GoogleAuthConfig).Marshal() + case "github": + result = new(auth.GithubAuthConfig).Marshal() + case "facebook": + result = new(auth.FacebookAuthConfig).Marshal() + case "telegram": + result = new(auth.TelegramAuthConfig).Marshal() + case "device": + result = new(auth.DeviceConfig).Marshal() + } + return result +} diff --git a/internal/logic/admin/authMethod/validate_test.go b/internal/logic/admin/authMethod/validate_test.go index 891eda2..7a2785e 100644 --- a/internal/logic/admin/authMethod/validate_test.go +++ b/internal/logic/admin/authMethod/validate_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/perfect-panel/ppanel-server/pkg/sms" + "github.com/perfect-panel/server/pkg/sms" ) func TestValidate(t *testing.T) { diff --git a/internal/logic/admin/console/queryRevenueStatisticsLogic.go b/internal/logic/admin/console/queryRevenueStatisticsLogic.go index 70e5e78..f9dbb37 100644 --- a/internal/logic/admin/console/queryRevenueStatisticsLogic.go +++ b/internal/logic/admin/console/queryRevenueStatisticsLogic.go @@ -2,12 +2,14 @@ package console import ( "context" + "os" + "strings" "time" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) @@ -17,7 +19,7 @@ type QueryRevenueStatisticsLogic struct { svcCtx *svc.ServiceContext } -// Query revenue statistics +// NewQueryRevenueStatisticsLogic Query revenue statistics func NewQueryRevenueStatisticsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryRevenueStatisticsLogic { return &QueryRevenueStatisticsLogic{ Logger: logger.WithContext(ctx), @@ -27,6 +29,9 @@ func NewQueryRevenueStatisticsLogic(ctx context.Context, svcCtx *svc.ServiceCont } func (l *QueryRevenueStatisticsLogic) QueryRevenueStatistics() (resp *types.RevenueStatisticsResponse, err error) { + if strings.ToLower(os.Getenv("PPANEL_MODE")) == "demo" { + return l.mockRevenueStatistics(), nil + } var today, monthly, all types.OrdersStatistics now := time.Now() @@ -45,8 +50,8 @@ func (l *QueryRevenueStatisticsLogic) QueryRevenueStatistics() (resp *types.Reve // Get monthly's revenue statistics monthlyData, err := l.svcCtx.OrderModel.QueryMonthlyOrders(l.ctx, now) if err != nil { - l.Errorw("[QueryRevenueStatisticsLogic] QueryDateOrders error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "QueryDateOrders error: %v", err) + l.Errorw("[QueryRevenueStatisticsLogic] QueryMonthlyOrders error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "QueryMonthlyOrders error: %v", err) } else { monthly = types.OrdersStatistics{ AmountTotal: monthlyData.AmountTotal, @@ -56,6 +61,24 @@ func (l *QueryRevenueStatisticsLogic) QueryRevenueStatistics() (resp *types.Reve } } + // Get monthly daily list for the current month (from 1st to current date) + monthlyListData, err := l.svcCtx.OrderModel.QueryDailyOrdersList(l.ctx, now) + if err != nil { + l.Errorw("[QueryRevenueStatisticsLogic] QueryDailyOrdersList error", logger.Field("error", err.Error())) + // Don't return error, just log it and continue with empty list + } else { + monthlyList := make([]types.OrdersStatistics, len(monthlyListData)) + for i, data := range monthlyListData { + monthlyList[i] = types.OrdersStatistics{ + Date: data.Date, + AmountTotal: data.AmountTotal, + NewOrderAmount: data.NewOrderAmount, + RenewalOrderAmount: data.RenewalOrderAmount, + } + } + monthly.List = monthlyList + } + // Get all revenue statistics allData, err := l.svcCtx.OrderModel.QueryTotalOrders(l.ctx) if err != nil { @@ -69,9 +92,79 @@ func (l *QueryRevenueStatisticsLogic) QueryRevenueStatistics() (resp *types.Reve List: make([]types.OrdersStatistics, 0), } } + + // Get all monthly list for the past 6 months + allListData, err := l.svcCtx.OrderModel.QueryMonthlyOrdersList(l.ctx, now) + if err != nil { + l.Errorw("[QueryRevenueStatisticsLogic] QueryMonthlyOrdersList error", logger.Field("error", err.Error())) + // Don't return error, just log it and continue with empty list + } else { + allList := make([]types.OrdersStatistics, len(allListData)) + for i, data := range allListData { + allList[i] = types.OrdersStatistics{ + Date: data.Date, + AmountTotal: data.AmountTotal, + NewOrderAmount: data.NewOrderAmount, + RenewalOrderAmount: data.RenewalOrderAmount, + } + } + all.List = allList + } + return &types.RevenueStatisticsResponse{ Today: today, Monthly: monthly, All: all, }, nil } + +// mockRevenueStatistics is a mock function to simulate revenue statistics data. +func (l *QueryRevenueStatisticsLogic) mockRevenueStatistics() *types.RevenueStatisticsResponse { + now := time.Now() + + // Generate daily data for the current month (from 1st to current date) + monthlyList := make([]types.OrdersStatistics, 7) + for i := 0; i < 7; i++ { + dayDate := now.AddDate(0, 0, -(6 - i)) + baseAmount := int64(25000 + ((6 - i) * 3000) + ((6-i)%3)*8000) + monthlyList[i] = types.OrdersStatistics{ + Date: dayDate.Format("2006-01-02"), + AmountTotal: baseAmount, + NewOrderAmount: int64(float64(baseAmount) * 0.68), + RenewalOrderAmount: int64(float64(baseAmount) * 0.32), + } + } + + // Generate monthly data for the past 6 months (oldest first) + allList := make([]types.OrdersStatistics, 6) + for i := 0; i < 6; i++ { + monthDate := now.AddDate(0, -(5 - i), 0) + baseAmount := int64(1800000 + ((5 - i) * 200000) + ((5-i)%2)*500000) + allList[i] = types.OrdersStatistics{ + Date: monthDate.Format("2006-01"), + AmountTotal: baseAmount, + NewOrderAmount: int64(float64(baseAmount) * 0.68), + RenewalOrderAmount: int64(float64(baseAmount) * 0.32), + } + } + + return &types.RevenueStatisticsResponse{ + Today: types.OrdersStatistics{ + AmountTotal: 35888, + NewOrderAmount: 22888, + RenewalOrderAmount: 13000, + }, + Monthly: types.OrdersStatistics{ + AmountTotal: 888888, + NewOrderAmount: 588888, + RenewalOrderAmount: 300000, + List: monthlyList, + }, + All: types.OrdersStatistics{ + AmountTotal: 12888888, + NewOrderAmount: 8588888, + RenewalOrderAmount: 4300000, + List: allList, + }, + } +} diff --git a/internal/logic/admin/console/queryServerTotalDataLogic.go b/internal/logic/admin/console/queryServerTotalDataLogic.go index 6ec85b4..5bdafca 100644 --- a/internal/logic/admin/console/queryServerTotalDataLogic.go +++ b/internal/logic/admin/console/queryServerTotalDataLogic.go @@ -2,11 +2,19 @@ package console import ( "context" + "os" + "strings" + "time" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/internal/model/node" + "github.com/perfect-panel/server/internal/model/traffic" + "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" + "gorm.io/gorm" ) type QueryServerTotalDataLogic struct { @@ -25,116 +33,269 @@ func NewQueryServerTotalDataLogic(ctx context.Context, svcCtx *svc.ServiceContex } func (l *QueryServerTotalDataLogic) QueryServerTotalData() (resp *types.ServerTotalDataResponse, err error) { - resp = &types.ServerTotalDataResponse{ - ServerTrafficRankingToday: make([]types.ServerTrafficData, 0), - ServerTrafficRankingYesterday: make([]types.ServerTrafficData, 0), - UserTrafficRankingToday: make([]types.UserTrafficData, 0), - UserTrafficRankingYesterday: make([]types.UserTrafficData, 0), + + if strings.ToLower(os.Getenv("PPANEL_MODE")) == "demo" { + return l.mockRevenueStatistics(), nil } - // Query node server status - servers, err := l.svcCtx.ServerModel.FindAllServer(l.ctx) - if err != nil { - l.Errorw("[QueryServerTotalDataLogic] FindAllServer error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(err, "FindAllServer error: %v", err) - } - onlineServers, err := l.svcCtx.NodeCache.GetOnlineNodeStatusCount(l.ctx) - if err != nil { - l.Errorw("[QueryServerTotalDataLogic] GetOnlineNodeStatusCount error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(err, "GetOnlineNodeStatusCount error: %v", err) - } - resp.OnlineServers = onlineServers - resp.OfflineServers = int64(len(servers) - int(onlineServers)) + now := time.Now() - // 获取所有节点在线用户 - allNodeOnlineUser, err := l.svcCtx.NodeCache.GetAllNodeOnlineUser(l.ctx) - if err != nil { - l.Errorw("[QueryServerTotalDataLogic] Get all node online user failed", logger.Field("error", err.Error())) - } - resp.OnlineUserIPs = int64(len(allNodeOnlineUser)) + todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + todayEnd := todayStart.Add(24 * time.Hour).Add(-time.Second) + query := l.svcCtx.DB.WithContext(l.ctx) + var todayTop10User []log.UserTraffic - // 获取所有节点今日上传下载流量 - allNodeUploadTraffic, err := l.svcCtx.NodeCache.GetAllNodeUploadTraffic(l.ctx) - if err != nil { - l.Errorw("[QueryServerTotalDataLogic] Get all node upload traffic failed", logger.Field("error", err.Error())) + err = query.Model(&traffic.TrafficLog{}). + Select("user_id, subscribe_id, SUM(download + upload) AS total, SUM(download) AS download, SUM(upload) AS upload"). + Where("timestamp BETWEEN ? AND ?", todayStart, todayEnd). + Group("user_id, subscribe_id"). + Order("total DESC"). + Limit(10). + Scan(&todayTop10User).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + logger.Errorf("[Traffic Stat Queue] Query user traffic failed: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), " Query user traffic failed: %v", err.Error()) } - resp.TodayUpload = allNodeUploadTraffic - allNodeDownloadTraffic, err := l.svcCtx.NodeCache.GetAllNodeDownloadTraffic(l.ctx) - if err != nil { - l.Errorw("[QueryServerTotalDataLogic] Get all node download traffic failed", logger.Field("error", err.Error())) + var userTodayTrafficRanking []types.UserTrafficData + for _, item := range todayTop10User { + userTodayTrafficRanking = append(userTodayTrafficRanking, types.UserTrafficData{ + SID: item.SubscribeId, + Upload: item.Upload, + Download: item.Download, + }) } - resp.TodayDownload = allNodeDownloadTraffic - // 获取节点流量排行榜 前10 - nodeTrafficRankingToday, err := l.svcCtx.NodeCache.GetNodeTodayTotalTrafficRank(l.ctx, 10) - if err != nil { - l.Errorw("[QueryServerTotalDataLogic] Get node today total traffic rank failed", logger.Field("error", err.Error())) + + // query yesterday user traffic rank log + yesterday := todayStart.Add(-24 * time.Hour).Format(time.DateOnly) + + var yesterdayLog log.SystemLog + err = query.Model(&log.SystemLog{}).Where("`date` = ? AND `type` = ?", yesterday, log.TypeUserTrafficRank).First(&yesterdayLog).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + l.Errorw("[QueryServerTotalDataLogic] Query yesterday user traffic rank log error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query yesterday user traffic rank log error: %v", err) } - if len(nodeTrafficRankingToday) > 0 { - var serverTrafficData []types.ServerTrafficData - for _, rank := range nodeTrafficRankingToday { - serverInfo, err := l.svcCtx.ServerModel.FindOne(l.ctx, rank.ID) + + var yesterdayUserRankData []types.UserTrafficData + if yesterdayLog.Id > 0 { + var rank log.UserTrafficRank + err = rank.Unmarshal([]byte(yesterdayLog.Content)) + if err != nil { + l.Errorw("[QueryServerTotalDataLogic] Unmarshal yesterday user traffic rank log error", logger.Field("error", err.Error())) + } + for _, v := range rank.Rank { + yesterdayUserRankData = append(yesterdayUserRankData, types.UserTrafficData{ + SID: v.SubscribeId, + Upload: v.Upload, + Download: v.Download, + }) + } + } + + // query server traffic rank today + var todayTop10Server []log.ServerTraffic + err = query.Model(&traffic.TrafficLog{}).Select("server_id, SUM(download + upload) AS total, SUM(download) AS download, SUM(upload) AS upload"). + Where("timestamp BETWEEN ? AND ?", todayStart, todayEnd). + Group("server_id"). + Order("total DESC"). + Limit(10). + Scan(&todayTop10Server).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + logger.Errorf("[Traffic Stat Queue] Query server traffic failed: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), " Query server traffic failed: %v", err.Error()) + } + + var todayServerRanking []types.ServerTrafficData + for _, item := range todayTop10Server { + info, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, item.ServerId) + if err != nil { + l.Errorw("[QueryServerTotalDataLogic] FindOneServer error", logger.Field("error", err.Error()), logger.Field("server_id", item.ServerId)) + continue + } + todayServerRanking = append(todayServerRanking, types.ServerTrafficData{ + ServerId: item.ServerId, + Name: info.Name, + Upload: item.Upload, + Download: item.Download, + }) + } + + // query server traffic rank yesterday + var yesterdayTop10Server []types.ServerTrafficData + var yesterdayServerTrafficLog log.SystemLog + err = query.Model(&log.SystemLog{}).Where("`date` = ? AND `type` = ?", yesterday, log.TypeServerTrafficRank).First(&yesterdayServerTrafficLog).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + l.Errorw("[QueryServerTotalDataLogic] Query yesterday server traffic rank log error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query yesterday server traffic rank log error: %v", err) + } + if yesterdayServerTrafficLog.Id > 0 { + var rank log.ServerTrafficRank + err = rank.Unmarshal([]byte(yesterdayServerTrafficLog.Content)) + if err != nil { + l.Errorw("[QueryServerTotalDataLogic] Unmarshal yesterday server traffic rank log error", logger.Field("error", err.Error())) + } + + for _, v := range rank.Rank { + info, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, v.ServerId) if err != nil { - l.Errorw("[QueryServerTotalDataLogic] FindOne error", logger.Field("error", err)) + l.Errorw("[QueryServerTotalDataLogic] FindOneServer error", logger.Field("error", err.Error()), logger.Field("server_id", v.ServerId)) continue } - serverTrafficData = append(serverTrafficData, types.ServerTrafficData{ - ServerId: rank.ID, - Name: serverInfo.Name, - Upload: rank.Upload, - Download: rank.Download, + yesterdayTop10Server = append(yesterdayTop10Server, types.ServerTrafficData{ + ServerId: v.ServerId, + Name: info.Name, + Upload: v.Upload, + Download: v.Download, }) } - resp.ServerTrafficRankingToday = serverTrafficData - } - // 获取用户流量排行榜 前10 - userTrafficRankingToday, err := l.svcCtx.NodeCache.GetUserTodayTotalTrafficRank(l.ctx, 10) - if err != nil { - l.Errorw("[QueryServerTotalDataLogic] Get user today total traffic rank failed", logger.Field("error", err.Error())) } - if len(userTrafficRankingToday) > 0 { - var userTrafficData []types.UserTrafficData - for _, rank := range userTrafficRankingToday { - userTrafficData = append(userTrafficData, types.UserTrafficData{ - SID: rank.SID, - Upload: rank.Upload, - Download: rank.Download, - }) - } - resp.UserTrafficRankingToday = userTrafficData - } - // 获取昨日节点流量排行榜 前10 - nodeTrafficRankingYesterday, err := l.svcCtx.NodeCache.GetYesterdayNodeTotalTrafficRank(l.ctx) + // query online user count + onlineUsers, err := l.svcCtx.NodeModel.OnlineUserSubscribeGlobal(l.ctx) if err != nil { - l.Errorw("[QueryServerTotalDataLogic] Get yesterday node total traffic rank failed", logger.Field("error", err.Error())) + l.Errorw("[QueryServerTotalDataLogic] OnlineUserSubscribeGlobal error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "OnlineUserSubscribeGlobal error: %v", err) } - if len(nodeTrafficRankingYesterday) > 0 { - var serverTrafficData []types.ServerTrafficData - for _, rank := range nodeTrafficRankingYesterday { - serverTrafficData = append(serverTrafficData, types.ServerTrafficData{ - ServerId: rank.ID, - Name: rank.Name, - Upload: rank.Upload, - Download: rank.Download, - }) + + // query online/offline server count + var onlineServers, offlineServers int64 + err = query.Model(&node.Server{}).Where("`last_reported_at` > ?", now.Add(-5*time.Minute)).Count(&onlineServers).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + l.Errorw("[QueryServerTotalDataLogic] Count online servers error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Count online servers error: %v", err) + } + + err = query.Model(&node.Server{}).Where("`last_reported_at` <= ? OR `last_reported_at` IS NULL", now.Add(-5*time.Minute)).Count(&offlineServers).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + l.Errorw("[QueryServerTotalDataLogic] Count offline servers error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Count offline servers error: %v", err) + } + // TodayUpload, TodayDownload, MonthlyUpload, MonthlyDownload + var todayUpload, todayDownload, monthlyUpload, monthlyDownload int64 + + type trafficSum struct { + Upload int64 + Download int64 + } + var todayTraffic trafficSum + // Today + err = query.Model(&traffic.TrafficLog{}).Select("SUM(upload) AS upload, SUM(download) AS download"). + Where("timestamp BETWEEN ? AND ?", todayStart, todayEnd). + Scan(&todayTraffic).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + l.Errorw("[QueryServerTotalDataLogic] Sum today traffic error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Sum today traffic error: %v", err) + } + todayUpload = todayTraffic.Upload + todayDownload = todayTraffic.Download + + // Monthly + monthlyUpload += todayUpload + monthlyDownload += todayDownload + + for i := now.Day() - 1; i >= 1; i-- { + var logInfo log.SystemLog + date := time.Date(now.Year(), now.Month(), i, 0, 0, 0, 0, now.Location()).Format(time.DateOnly) + err = query.Model(&log.SystemLog{}).Where("`date` = ? AND `type` = ?", date, log.TypeTrafficStat).First(&logInfo).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + l.Errorw("[QueryServerTotalDataLogic] Query daily traffic stat log error", logger.Field("error", err.Error()), logger.Field("date", date)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query daily traffic stat log error: %v", err) } - resp.ServerTrafficRankingYesterday = serverTrafficData - } - // 获取昨日用户流量排行榜 前10 - userTrafficRankingYesterday, err := l.svcCtx.NodeCache.GetYesterdayUserTotalTrafficRank(l.ctx) - if err != nil { - l.Errorw("[QueryServerTotalDataLogic] Get yesterday user total traffic rank failed", logger.Field("error", err.Error())) - } - if len(userTrafficRankingYesterday) > 0 { - var userTrafficData []types.UserTrafficData - for _, rank := range userTrafficRankingYesterday { - userTrafficData = append(userTrafficData, types.UserTrafficData{ - SID: rank.SID, - Upload: rank.Upload, - Download: rank.Download, - }) + if logInfo.Id > 0 { + var stat log.TrafficStat + err = stat.Unmarshal([]byte(logInfo.Content)) + if err != nil { + l.Errorw("[QueryServerTotalDataLogic] Unmarshal daily traffic stat log error", logger.Field("error", err.Error()), logger.Field("date", date)) + continue + } + monthlyUpload += stat.Upload + monthlyDownload += stat.Download } - resp.UserTrafficRankingYesterday = userTrafficData } + + resp = &types.ServerTotalDataResponse{ + OnlineUsers: onlineUsers, + OnlineServers: onlineServers, + OfflineServers: offlineServers, + TodayUpload: todayUpload, + TodayDownload: todayDownload, + MonthlyUpload: monthlyUpload, + MonthlyDownload: monthlyDownload, + UpdatedAt: now.Unix(), + ServerTrafficRankingToday: todayServerRanking, + ServerTrafficRankingYesterday: yesterdayTop10Server, + UserTrafficRankingToday: userTodayTrafficRanking, + UserTrafficRankingYesterday: yesterdayUserRankData, + } + return resp, nil } + +func (l *QueryServerTotalDataLogic) mockRevenueStatistics() *types.ServerTotalDataResponse { + now := time.Now() + + // Generate server traffic ranking data for today (top 10) + serverTrafficToday := make([]types.ServerTrafficData, 10) + serverNames := []string{"香港-01", "美国-洛杉矶", "日本-东京", "新加坡-01", "韩国-首尔", "台湾-01", "德国-法兰克福", "英国-伦敦", "加拿大-多伦多", "澳洲-悉尼"} + for i := 0; i < 10; i++ { + upload := int64(500000000 + (i * 100000000) + (i%3)*200000000) // 500MB - 1.5GB + download := int64(2000000000 + (i * 300000000) + (i%4)*500000000) // 2GB - 8GB + serverTrafficToday[i] = types.ServerTrafficData{ + ServerId: int64(i + 1), + Name: serverNames[i], + Upload: upload, + Download: download, + } + } + + // Generate server traffic ranking data for yesterday (top 10) + serverTrafficYesterday := make([]types.ServerTrafficData, 10) + for i := 0; i < 10; i++ { + upload := int64(480000000 + (i * 95000000) + (i%3)*180000000) + download := int64(1900000000 + (i * 280000000) + (i%4)*450000000) + serverTrafficYesterday[i] = types.ServerTrafficData{ + ServerId: int64(i + 1), + Name: serverNames[i], + Upload: upload, + Download: download, + } + } + + //// Generate user traffic ranking data for today (top 10) + //userTrafficToday := make([]types.UserTrafficData, 10) + //for i := 0; i < 10; i++ { + // upload := int64(100000000 + (i*20000000) + (i%5)*50000000) // 100MB - 400MB + // download := int64(800000000 + (i*150000000) + (i%3)*300000000) // 800MB - 3GB + // userTrafficToday[i] = types.UserTrafficData{ + // SID: int64(10001 + i), + // Upload: upload, + // Download: download, + // } + //} + + //// Generate user traffic ranking data for yesterday (top 10) + //userTrafficYesterday := make([]types.UserTrafficData, 10) + //for i := 0; i < 10; i++ { + // upload := int64(95000000 + (i*18000000) + (i%5)*45000000) + // download := int64(750000000 + (i*140000000) + (i%3)*280000000) + // userTrafficYesterday[i] = types.UserTrafficData{ + // SID: int64(10001 + i), + // Upload: upload, + // Download: download, + // } + //} + // + return &types.ServerTotalDataResponse{ + OnlineUsers: 1688, + OnlineServers: 8, + OfflineServers: 2, + TodayUpload: 8888888888, // ~8.3GB + TodayDownload: 28888888888, // ~26.9GB + MonthlyUpload: 288888888888, // ~269GB + MonthlyDownload: 888888888888, // ~828GB + UpdatedAt: now.Unix(), + ServerTrafficRankingToday: serverTrafficToday, + ServerTrafficRankingYesterday: serverTrafficYesterday, + //UserTrafficRankingToday: userTrafficToday, + //UserTrafficRankingYesterday: userTrafficYesterday, + } +} diff --git a/internal/logic/admin/console/queryTicketWaitReplyLogic.go b/internal/logic/admin/console/queryTicketWaitReplyLogic.go index e65984f..01c0eb3 100644 --- a/internal/logic/admin/console/queryTicketWaitReplyLogic.go +++ b/internal/logic/admin/console/queryTicketWaitReplyLogic.go @@ -3,9 +3,9 @@ package console import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type QueryTicketWaitReplyLogic struct { diff --git a/internal/logic/admin/console/queryUserStatisticsLogic.go b/internal/logic/admin/console/queryUserStatisticsLogic.go index 4e1d221..746176c 100644 --- a/internal/logic/admin/console/queryUserStatisticsLogic.go +++ b/internal/logic/admin/console/queryUserStatisticsLogic.go @@ -2,11 +2,13 @@ package console import ( "context" + "os" + "strings" "time" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type QueryUserStatisticsLogic struct { @@ -25,6 +27,9 @@ func NewQueryUserStatisticsLogic(ctx context.Context, svcCtx *svc.ServiceContext } func (l *QueryUserStatisticsLogic) QueryUserStatistics() (resp *types.UserStatisticsResponse, err error) { + if strings.ToLower(os.Getenv("PPANEL_MODE")) == "demo" { + return l.mockRevenueStatistics(), nil + } resp = &types.UserStatisticsResponse{} now := time.Now() // query today user register count @@ -56,8 +61,24 @@ func (l *QueryUserStatisticsLogic) QueryUserStatistics() (resp *types.UserStatis } else { resp.Monthly.NewOrderUsers = newMonth resp.Monthly.RenewalOrderUsers = renewalMonth - // TODO: Check the purchase status in the past seven days - resp.Monthly.List = make([]types.UserStatistics, 0) + } + + // Get monthly daily user statistics list for the current month (from 1st to current date) + monthlyListData, err := l.svcCtx.UserModel.QueryDailyUserStatisticsList(l.ctx, now) + if err != nil { + l.Errorw("[QueryUserStatisticsLogic] QueryDailyUserStatisticsList error", logger.Field("error", err.Error())) + // Don't return error, just log it and continue with empty list + } else { + monthlyList := make([]types.UserStatistics, len(monthlyListData)) + for i, data := range monthlyListData { + monthlyList[i] = types.UserStatistics{ + Date: data.Date, + Register: data.Register, + NewOrderUsers: data.NewOrderUsers, + RenewalOrderUsers: data.RenewalOrderUsers, + } + } + resp.Monthly.List = monthlyList } // query all user count @@ -67,5 +88,83 @@ func (l *QueryUserStatisticsLogic) QueryUserStatistics() (resp *types.UserStatis } else { resp.All.Register = allUserCount } + + // query all user order counts + allNewOrderUsers, allRenewalOrderUsers, err := l.svcCtx.OrderModel.QueryTotalUserCounts(l.ctx) + if err != nil { + l.Errorw("[QueryUserStatisticsLogic] QueryTotalUserCounts error", logger.Field("error", err.Error())) + } else { + resp.All.NewOrderUsers = allNewOrderUsers + resp.All.RenewalOrderUsers = allRenewalOrderUsers + } + + // Get all monthly user statistics list for the past 6 months + allListData, err := l.svcCtx.UserModel.QueryMonthlyUserStatisticsList(l.ctx, now) + if err != nil { + l.Errorw("[QueryUserStatisticsLogic] QueryMonthlyUserStatisticsList error", logger.Field("error", err.Error())) + // Don't return error, just log it and continue with empty list + } else { + allList := make([]types.UserStatistics, len(allListData)) + for i, data := range allListData { + allList[i] = types.UserStatistics{ + Date: data.Date, + Register: data.Register, + NewOrderUsers: data.NewOrderUsers, + RenewalOrderUsers: data.RenewalOrderUsers, + } + } + resp.All.List = allList + } + return } + +func (l *QueryUserStatisticsLogic) mockRevenueStatistics() *types.UserStatisticsResponse { + now := time.Now() + + // Generate daily user statistics for the current month (from 1st to current date) + monthlyList := make([]types.UserStatistics, 7) + for i := 0; i < 7; i++ { + dayDate := now.AddDate(0, 0, -(6 - i)) + baseRegister := int64(18 + ((6 - i) * 3) + ((6-i)%3)*8) + monthlyList[i] = types.UserStatistics{ + Date: dayDate.Format("2006-01-02"), + Register: baseRegister, + NewOrderUsers: int64(float64(baseRegister) * 0.65), + RenewalOrderUsers: int64(float64(baseRegister) * 0.35), + } + } + + // Generate monthly user statistics for the past 6 months (oldest first) + allList := make([]types.UserStatistics, 6) + for i := 0; i < 6; i++ { + monthDate := now.AddDate(0, -(5 - i), 0) + baseRegister := int64(1800 + ((5 - i) * 200) + ((5-i)%2)*500) + allList[i] = types.UserStatistics{ + Date: monthDate.Format("2006-01"), + Register: baseRegister, + NewOrderUsers: int64(float64(baseRegister) * 0.65), + RenewalOrderUsers: int64(float64(baseRegister) * 0.35), + } + } + + return &types.UserStatisticsResponse{ + Today: types.UserStatistics{ + Register: 28, + NewOrderUsers: 18, + RenewalOrderUsers: 10, + }, + Monthly: types.UserStatistics{ + Register: 888, + NewOrderUsers: 588, + RenewalOrderUsers: 300, + List: monthlyList, + }, + All: types.UserStatistics{ + Register: 18888, + NewOrderUsers: 0, // This field is not used in All statistics + RenewalOrderUsers: 0, // This field is not used in All statistics + List: allList, + }, + } +} diff --git a/internal/logic/admin/coupon/batchDeleteCouponLogic.go b/internal/logic/admin/coupon/batchDeleteCouponLogic.go index 9e9da2c..afc229f 100644 --- a/internal/logic/admin/coupon/batchDeleteCouponLogic.go +++ b/internal/logic/admin/coupon/batchDeleteCouponLogic.go @@ -3,10 +3,10 @@ package coupon import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/coupon/createCouponLogic.go b/internal/logic/admin/coupon/createCouponLogic.go index 26ff184..d83f28a 100644 --- a/internal/logic/admin/coupon/createCouponLogic.go +++ b/internal/logic/admin/coupon/createCouponLogic.go @@ -5,14 +5,14 @@ import ( "math/rand" "time" - "github.com/perfect-panel/ppanel-server/internal/model/coupon" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/random" - "github.com/perfect-panel/ppanel-server/pkg/snowflake" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/coupon" + "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/random" + "github.com/perfect-panel/server/pkg/snowflake" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) diff --git a/internal/logic/admin/coupon/deleteCouponLogic.go b/internal/logic/admin/coupon/deleteCouponLogic.go index b041ecd..f50da63 100644 --- a/internal/logic/admin/coupon/deleteCouponLogic.go +++ b/internal/logic/admin/coupon/deleteCouponLogic.go @@ -3,10 +3,10 @@ package coupon import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/coupon/getCouponListLogic.go b/internal/logic/admin/coupon/getCouponListLogic.go index c58e015..125608d 100644 --- a/internal/logic/admin/coupon/getCouponListLogic.go +++ b/internal/logic/admin/coupon/getCouponListLogic.go @@ -3,11 +3,11 @@ package coupon import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/coupon/updateCouponLogic.go b/internal/logic/admin/coupon/updateCouponLogic.go index ee3136a..e4b3712 100644 --- a/internal/logic/admin/coupon/updateCouponLogic.go +++ b/internal/logic/admin/coupon/updateCouponLogic.go @@ -4,12 +4,12 @@ import ( "context" "fmt" - "github.com/perfect-panel/ppanel-server/internal/model/coupon" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/coupon" + "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" ) diff --git a/internal/logic/admin/document/batchDeleteDocumentLogic.go b/internal/logic/admin/document/batchDeleteDocumentLogic.go index 3a53f46..2514a3b 100644 --- a/internal/logic/admin/document/batchDeleteDocumentLogic.go +++ b/internal/logic/admin/document/batchDeleteDocumentLogic.go @@ -3,10 +3,10 @@ package document import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/document/createDocumentLogic.go b/internal/logic/admin/document/createDocumentLogic.go index f30a0cd..88ae718 100644 --- a/internal/logic/admin/document/createDocumentLogic.go +++ b/internal/logic/admin/document/createDocumentLogic.go @@ -4,11 +4,11 @@ import ( "context" "strings" - "github.com/perfect-panel/ppanel-server/internal/model/document" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/document" + "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" ) diff --git a/internal/logic/admin/document/deleteDocumentLogic.go b/internal/logic/admin/document/deleteDocumentLogic.go index f264d05..a01eb04 100644 --- a/internal/logic/admin/document/deleteDocumentLogic.go +++ b/internal/logic/admin/document/deleteDocumentLogic.go @@ -3,10 +3,10 @@ package document import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/document/getDocumentDetailLogic.go b/internal/logic/admin/document/getDocumentDetailLogic.go index 7b94a61..55e2a76 100644 --- a/internal/logic/admin/document/getDocumentDetailLogic.go +++ b/internal/logic/admin/document/getDocumentDetailLogic.go @@ -3,11 +3,11 @@ package document import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/document/getDocumentListLogic.go b/internal/logic/admin/document/getDocumentListLogic.go index 1aa88db..87294d7 100644 --- a/internal/logic/admin/document/getDocumentListLogic.go +++ b/internal/logic/admin/document/getDocumentListLogic.go @@ -3,11 +3,11 @@ package document import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/document/updateDocumentLogic.go b/internal/logic/admin/document/updateDocumentLogic.go index e741028..5f1d191 100644 --- a/internal/logic/admin/document/updateDocumentLogic.go +++ b/internal/logic/admin/document/updateDocumentLogic.go @@ -4,11 +4,11 @@ import ( "context" "strings" - "github.com/perfect-panel/ppanel-server/internal/model/document" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/document" + "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" ) diff --git a/internal/logic/admin/log/filterBalanceLogLogic.go b/internal/logic/admin/log/filterBalanceLogLogic.go new file mode 100644 index 0000000..6393d66 --- /dev/null +++ b/internal/logic/admin/log/filterBalanceLogLogic.go @@ -0,0 +1,64 @@ +package log + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/log" + "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 FilterBalanceLogLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewFilterBalanceLogLogic Filter balance log +func NewFilterBalanceLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterBalanceLogLogic { + return &FilterBalanceLogLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *FilterBalanceLogLogic) FilterBalanceLog(req *types.FilterBalanceLogRequest) (resp *types.FilterBalanceLogResponse, err error) { + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Type: log.TypeBalance.Uint8(), + Data: req.Date, + ObjectID: req.UserId, + }) + + if err != nil { + l.Errorw("[FilterBalanceLog] Query User Balance Log Error:", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query User Balance Log Error") + } + + list := make([]types.BalanceLog, 0) + for _, datum := range data { + var content log.Balance + if err = content.Unmarshal([]byte(datum.Content)); err != nil { + l.Errorf("[QueryUserBalanceLog] unmarshal balance log content failed: %v", err.Error()) + continue + } + list = append(list, types.BalanceLog{ + UserId: datum.ObjectID, + Amount: content.Amount, + Type: content.Type, + OrderNo: content.OrderNo, + Balance: content.Balance, + Timestamp: content.Timestamp, + }) + } + + return &types.FilterBalanceLogResponse{ + Total: total, + List: list, + }, nil +} diff --git a/internal/logic/admin/log/filterCommissionLogLogic.go b/internal/logic/admin/log/filterCommissionLogLogic.go new file mode 100644 index 0000000..6e4020d --- /dev/null +++ b/internal/logic/admin/log/filterCommissionLogLogic.go @@ -0,0 +1,61 @@ +package log + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/log" + "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 FilterCommissionLogLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewFilterCommissionLogLogic Filter commission log +func NewFilterCommissionLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterCommissionLogLogic { + return &FilterCommissionLogLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *FilterCommissionLogLogic) FilterCommissionLog(req *types.FilterCommissionLogRequest) (resp *types.FilterCommissionLogResponse, err error) { + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Data: req.Date, + Type: log.TypeCommission.Uint8(), + ObjectID: req.UserId, + }) + if err != nil { + l.Errorw("Query User Commission Log failed", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query User Commission Log failed") + } + var list []types.CommissionLog + + for _, datum := range data { + var content log.Commission + if err = content.Unmarshal([]byte(datum.Content)); err != nil { + l.Errorf("unmarshal commission log content failed: %v", err.Error()) + continue + } + list = append(list, types.CommissionLog{ + UserId: datum.ObjectID, + Type: content.Type, + Amount: content.Amount, + OrderNo: content.OrderNo, + Timestamp: content.Timestamp, + }) + } + return &types.FilterCommissionLogResponse{ + Total: total, + List: list, + }, nil +} diff --git a/internal/logic/admin/log/filterEmailLogLogic.go b/internal/logic/admin/log/filterEmailLogLogic.go new file mode 100644 index 0000000..21ce204 --- /dev/null +++ b/internal/logic/admin/log/filterEmailLogLogic.go @@ -0,0 +1,68 @@ +package log + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/log" + "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 FilterEmailLogLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewFilterEmailLogLogic Filter email log +func NewFilterEmailLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterEmailLogLogic { + return &FilterEmailLogLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *FilterEmailLogLogic) FilterEmailLog(req *types.FilterLogParams) (resp *types.FilterEmailLogResponse, err error) { + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Type: log.TypeEmailMessage.Uint8(), + Data: req.Date, + Search: req.Search, + }) + + if err != nil { + l.Errorf("[FilterEmailLog] failed to filter system log: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to filter system log: %v", err.Error()) + } + + var list []types.MessageLog + + for _, datum := range data { + var content log.Message + err = content.Unmarshal([]byte(datum.Content)) + if err != nil { + l.Errorf("[FilterEmailLog] failed to unmarshal content: %v", err.Error()) + continue + } + list = append(list, types.MessageLog{ + Id: datum.Id, + Type: datum.Type, + Platform: content.Platform, + To: content.To, + Subject: content.Subject, + Content: content.Content, + Status: content.Status, + CreatedAt: datum.CreatedAt.UnixMilli(), + }) + } + + return &types.FilterEmailLogResponse{ + Total: total, + List: list, + }, nil +} diff --git a/internal/logic/admin/log/filterGiftLogLogic.go b/internal/logic/admin/log/filterGiftLogLogic.go new file mode 100644 index 0000000..5b23119 --- /dev/null +++ b/internal/logic/admin/log/filterGiftLogLogic.go @@ -0,0 +1,68 @@ +package log + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/log" + "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 FilterGiftLogLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// Filter gift log +func NewFilterGiftLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterGiftLogLogic { + return &FilterGiftLogLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *FilterGiftLogLogic) FilterGiftLog(req *types.FilterGiftLogRequest) (resp *types.FilterGiftLogResponse, err error) { + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Type: log.TypeGift.Uint8(), + ObjectID: req.UserId, + Data: req.Date, + Search: req.Search, + }) + + if err != nil { + l.Errorf("[FilterGiftLog] failed to filter system log: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to filter system log: %v", err.Error()) + } + + var list []types.GiftLog + for _, datum := range data { + var content log.Gift + err = content.Unmarshal([]byte(datum.Content)) + if err != nil { + l.Errorf("[FilterGiftLog] failed to unmarshal content: %v", err.Error()) + continue + } + list = append(list, types.GiftLog{ + Type: content.Type, + UserId: datum.ObjectID, + OrderNo: content.OrderNo, + SubscribeId: content.SubscribeId, + Amount: content.Amount, + Balance: content.Balance, + Remark: content.Remark, + Timestamp: content.Timestamp, + }) + } + + return &types.FilterGiftLogResponse{ + Total: total, + List: list, + }, nil +} diff --git a/internal/logic/admin/log/filterLoginLogLogic.go b/internal/logic/admin/log/filterLoginLogLogic.go new file mode 100644 index 0000000..3a43941 --- /dev/null +++ b/internal/logic/admin/log/filterLoginLogLogic.go @@ -0,0 +1,65 @@ +package log + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/log" + "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 FilterLoginLogLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewFilterLoginLogLogic Filter login log +func NewFilterLoginLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterLoginLogLogic { + return &FilterLoginLogLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *FilterLoginLogLogic) FilterLoginLog(req *types.FilterLoginLogRequest) (resp *types.FilterLoginLogResponse, err error) { + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Type: log.TypeLogin.Uint8(), + ObjectID: req.UserId, + Data: req.Date, + Search: req.Search, + }) + + if err != nil { + l.Errorf("[FilterLoginLog] failed to filter system log: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to filter system log: %v", err.Error()) + } + var list []types.LoginLog + for _, datum := range data { + var item log.Login + err = item.Unmarshal([]byte(datum.Content)) + if err != nil { + l.Errorf("[FilterLoginLog] failed to unmarshal content: %v", err.Error()) + continue + } + list = append(list, types.LoginLog{ + UserId: datum.ObjectID, + Method: item.Method, + LoginIP: item.LoginIP, + UserAgent: item.UserAgent, + Success: item.Success, + Timestamp: datum.CreatedAt.UnixMilli(), + }) + } + + return &types.FilterLoginLogResponse{ + Total: total, + List: list, + }, nil +} diff --git a/internal/logic/admin/log/filterMobileLogLogic.go b/internal/logic/admin/log/filterMobileLogLogic.go new file mode 100644 index 0000000..f5f0f4c --- /dev/null +++ b/internal/logic/admin/log/filterMobileLogLogic.go @@ -0,0 +1,68 @@ +package log + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/log" + "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 FilterMobileLogLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// Filter mobile log +func NewFilterMobileLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterMobileLogLogic { + return &FilterMobileLogLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *FilterMobileLogLogic) FilterMobileLog(req *types.FilterLogParams) (resp *types.FilterMobileLogResponse, err error) { + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Type: log.TypeMobileMessage.Uint8(), + Data: req.Date, + Search: req.Search, + }) + + if err != nil { + l.Errorf("[FilterMobileLog] failed to filter system log: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to filter system log: %v", err.Error()) + } + + var list []types.MessageLog + + for _, datum := range data { + var content log.Message + err = content.Unmarshal([]byte(datum.Content)) + if err != nil { + l.Errorf("[FilterMobileLog] failed to unmarshal content: %v", err.Error()) + continue + } + list = append(list, types.MessageLog{ + Id: datum.Id, + Type: datum.Type, + Platform: content.Platform, + To: content.To, + Subject: content.Subject, + Content: content.Content, + Status: content.Status, + CreatedAt: datum.CreatedAt.UnixMilli(), + }) + } + + return &types.FilterMobileLogResponse{ + Total: total, + List: list, + }, nil +} diff --git a/internal/logic/admin/log/filterRegisterLogLogic.go b/internal/logic/admin/log/filterRegisterLogLogic.go new file mode 100644 index 0000000..81c9684 --- /dev/null +++ b/internal/logic/admin/log/filterRegisterLogLogic.go @@ -0,0 +1,66 @@ +package log + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/log" + "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 FilterRegisterLogLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// Filter register log +func NewFilterRegisterLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterRegisterLogLogic { + return &FilterRegisterLogLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *FilterRegisterLogLogic) FilterRegisterLog(req *types.FilterRegisterLogRequest) (resp *types.FilterRegisterLogResponse, err error) { + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Type: log.TypeRegister.Uint8(), + ObjectID: req.UserId, + Data: req.Date, + Search: req.Search, + }) + + if err != nil { + l.Errorf("[FilterRegisterLog] failed to filter system log: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to filter system log: %v", err.Error()) + } + + var list []types.RegisterLog + for _, datum := range data { + var item log.Register + err = item.Unmarshal([]byte(datum.Content)) + if err != nil { + l.Errorf("[FilterLoginLog] failed to unmarshal content: %v", err.Error()) + continue + } + list = append(list, types.RegisterLog{ + UserId: datum.ObjectID, + AuthMethod: item.AuthMethod, + Identifier: item.Identifier, + RegisterIP: item.RegisterIP, + UserAgent: item.UserAgent, + Timestamp: item.Timestamp, + }) + } + + return &types.FilterRegisterLogResponse{ + List: list, + Total: total, + }, nil +} diff --git a/internal/logic/admin/log/filterResetSubscribeLogLogic.go b/internal/logic/admin/log/filterResetSubscribeLogLogic.go new file mode 100644 index 0000000..31e2d2a --- /dev/null +++ b/internal/logic/admin/log/filterResetSubscribeLogLogic.go @@ -0,0 +1,66 @@ +package log + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/log" + "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 FilterResetSubscribeLogLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewFilterResetSubscribeLogLogic Filter reset subscribe log +func NewFilterResetSubscribeLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterResetSubscribeLogLogic { + return &FilterResetSubscribeLogLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *FilterResetSubscribeLogLogic) FilterResetSubscribeLog(req *types.FilterResetSubscribeLogRequest) (resp *types.FilterResetSubscribeLogResponse, err error) { + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Type: log.TypeResetSubscribe.Uint8(), + ObjectID: req.UserSubscribeId, + Data: req.Date, + Search: req.Search, + }) + + if err != nil { + l.Errorf("[FilterResetSubscribeLog] failed to filter system log: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to filter system log: %v", err.Error()) + } + + var list []types.ResetSubscribeLog + + for _, item := range data { + var content log.ResetSubscribe + err = content.Unmarshal([]byte(item.Content)) + if err != nil { + l.Errorf("[FilterResetSubscribeLog] failed to unmarshal content: %v", err.Error()) + continue + } + list = append(list, types.ResetSubscribeLog{ + Type: content.Type, + UserId: content.UserId, + UserSubscribeId: item.ObjectID, + OrderNo: content.OrderNo, + Timestamp: content.Timestamp, + }) + } + + return &types.FilterResetSubscribeLogResponse{ + List: list, + Total: total, + }, nil +} diff --git a/internal/logic/admin/log/filterServerTrafficLogLogic.go b/internal/logic/admin/log/filterServerTrafficLogLogic.go new file mode 100644 index 0000000..df5ce41 --- /dev/null +++ b/internal/logic/admin/log/filterServerTrafficLogLogic.go @@ -0,0 +1,166 @@ +package log + +import ( + "context" + "time" + + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/internal/model/traffic" + "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 FilterServerTrafficLogLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewFilterServerTrafficLogLogic Filter server traffic log +func NewFilterServerTrafficLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterServerTrafficLogLogic { + return &FilterServerTrafficLogLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} +func (l *FilterServerTrafficLogLogic) FilterServerTrafficLog(req *types.FilterServerTrafficLogRequest) (resp *types.FilterServerTrafficLogResponse, err error) { + today := time.Now().Format("2006-01-02") + var list []types.ServerTrafficLog + var total int64 + + if req.Date == today || req.Date == "" { + now := time.Now() + start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local) + end := start.Add(24 * time.Hour).Add(-time.Nanosecond) + + var serverTraffic []log.ServerTraffic + err = l.svcCtx.DB.WithContext(l.ctx). + Model(&traffic.TrafficLog{}). + Select("server_id, SUM(download + upload) AS total, SUM(download) AS download, SUM(upload) AS upload"). + Where("timestamp BETWEEN ? AND ?", start, end). + Group("server_id"). + Order("SUM(download + upload) DESC"). + Scan(&serverTraffic).Error + if err != nil { + l.Errorw("[FilterServerTrafficLog] Query Database Error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "today traffic query error: %s", err.Error()) + } + + for _, v := range serverTraffic { + list = append(list, types.ServerTrafficLog{ + ServerId: v.ServerId, + Upload: v.Upload, + Download: v.Download, + Total: v.Total, + Date: today, + Details: true, + }) + } + + todayTotal := len(list) + + startIdx := (req.Page - 1) * req.Size + endIdx := startIdx + req.Size + + if startIdx < todayTotal { + if endIdx > todayTotal { + endIdx = todayTotal + } + pageData := list[startIdx:endIdx] + return &types.FilterServerTrafficLogResponse{ + List: pageData, + Total: int64(todayTotal), + }, nil + } + + need := endIdx - todayTotal + historyPage := (need + req.Size - 1) / req.Size // 算出需要的历史页数 + historyData, historyTotal, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: historyPage, + Size: need, + Type: log.TypeServerTraffic.Uint8(), + }) + if err != nil { + l.Errorw("[FilterServerTrafficLog] Query History Error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "history query error: %s", err.Error()) + } + + for _, item := range historyData { + var content log.ServerTraffic + if err = content.Unmarshal([]byte(item.Content)); err != nil { + l.Errorw("[FilterServerTrafficLog] Unmarshal Error", logger.Field("error", err.Error()), logger.Field("content", item.Content)) + continue + } + + hasDetails := true + if l.svcCtx.Config.Log.AutoClear { + last := now.AddDate(0, 0, int(-l.svcCtx.Config.Log.ClearDays)) + dataTime, err := time.Parse(time.DateOnly, item.Date) + if err != nil { + l.Errorw("[FilterServerTrafficLog] Parse Date Error", logger.Field("error", err.Error()), logger.Field("date", item.Date)) + } else { + if dataTime.Before(last) { + hasDetails = false + } else { + hasDetails = true + } + } + } + + list = append(list, types.ServerTrafficLog{ + ServerId: item.ObjectID, + Upload: content.Upload, + Download: content.Download, + Total: content.Total, + Date: item.Date, + Details: hasDetails, + }) + } + + // 返回最终分页数据 + if endIdx > len(list) { + endIdx = len(list) + } + pageData := list[startIdx:endIdx] + + return &types.FilterServerTrafficLogResponse{ + List: pageData, + Total: int64(todayTotal) + historyTotal, + }, nil + } + + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Type: log.TypeServerTraffic.Uint8(), + }) + if err != nil { + l.Errorw("[FilterServerTrafficLog] Query Database Error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "history query error: %s", err.Error()) + } + + for _, item := range data { + var content log.ServerTraffic + if err = content.Unmarshal([]byte(item.Content)); err != nil { + l.Errorw("[FilterServerTrafficLog] Unmarshal Error", logger.Field("error", err.Error()), logger.Field("content", item.Content)) + continue + } + list = append(list, types.ServerTrafficLog{ + ServerId: item.ObjectID, + Upload: content.Upload, + Download: content.Download, + Total: content.Total, + Date: item.Date, + Details: false, + }) + } + + return &types.FilterServerTrafficLogResponse{ + List: list, + Total: total, + }, nil +} diff --git a/internal/logic/admin/log/filterSubscribeLogLogic.go b/internal/logic/admin/log/filterSubscribeLogLogic.go new file mode 100644 index 0000000..560b145 --- /dev/null +++ b/internal/logic/admin/log/filterSubscribeLogLogic.go @@ -0,0 +1,71 @@ +package log + +import ( + "context" + "strconv" + + "github.com/perfect-panel/server/internal/model/log" + "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 FilterSubscribeLogLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewFilterSubscribeLogLogic Filter subscribe log +func NewFilterSubscribeLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterSubscribeLogLogic { + return &FilterSubscribeLogLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *FilterSubscribeLogLogic) FilterSubscribeLog(req *types.FilterSubscribeLogRequest) (resp *types.FilterSubscribeLogResponse, err error) { + params := &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Type: log.TypeSubscribe.Uint8(), + Data: req.Date, + ObjectID: req.UserId, + } + + if req.UserSubscribeId != 0 { + params.Search = `"user_subscribe_id":` + strconv.FormatInt(req.UserSubscribeId, 10) + } + + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, params) + if err != nil { + l.Errorf("[FilterSubscribeLog] failed to filter system log: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to filter system log") + } + + var list []types.SubscribeLog + for _, datum := range data { + var content log.Subscribe + err = content.Unmarshal([]byte(datum.Content)) + if err != nil { + l.Errorf("[FilterSubscribeLog] failed to unmarshal content: %v", err.Error()) + continue + } + list = append(list, types.SubscribeLog{ + UserId: datum.ObjectID, + Token: content.Token, + UserAgent: content.UserAgent, + ClientIP: content.ClientIP, + UserSubscribeId: content.UserSubscribeId, + Timestamp: datum.CreatedAt.UnixMilli(), + }) + } + + return &types.FilterSubscribeLogResponse{ + Total: total, + List: list, + }, nil +} diff --git a/internal/logic/admin/log/filterTrafficLogDetailsLogic.go b/internal/logic/admin/log/filterTrafficLogDetailsLogic.go new file mode 100644 index 0000000..0dea661 --- /dev/null +++ b/internal/logic/admin/log/filterTrafficLogDetailsLogic.go @@ -0,0 +1,84 @@ +package log + +import ( + "context" + "time" + + "github.com/perfect-panel/server/internal/model/traffic" + "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 FilterTrafficLogDetailsLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewFilterTrafficLogDetailsLogic Filter traffic log details +func NewFilterTrafficLogDetailsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterTrafficLogDetailsLogic { + return &FilterTrafficLogDetailsLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *FilterTrafficLogDetailsLogic) FilterTrafficLogDetails(req *types.FilterTrafficLogDetailsRequest) (resp *types.FilterTrafficLogDetailsResponse, err error) { + var start, end time.Time + if req.Date != "" { + day, err := time.ParseInLocation("2006-01-02", req.Date, time.Local) + if err != nil { + l.Errorw("[FilterTrafficLogDetails] Date Parse Error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), " date parse error: %s", err.Error()) + } + start = day + end = day.Add(24*time.Hour - time.Nanosecond) + } else { + // query today + now := time.Now() + start = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + end = start.Add(24*time.Hour - time.Nanosecond) + } + var data []*traffic.TrafficLog + tx := l.svcCtx.DB.WithContext(l.ctx).Model(&traffic.TrafficLog{}) + if req.ServerId != 0 { + tx = tx.Where("server_id = ?", req.ServerId) + } + if !start.IsZero() && !end.IsZero() { + tx = tx.Where("timestamp BETWEEN ? AND ?", start, end) + } + if req.UserId != 0 { + tx = tx.Where("user_id = ?", req.UserId) + } + if req.SubscribeId != 0 { + tx = tx.Where("subscribe_id = ?", req.SubscribeId) + } + var total int64 + err = tx.Count(&total).Limit(req.Size).Offset((req.Page - 1) * req.Size).Find(&data).Error + if err != nil { + l.Errorw("[FilterTrafficLogDetails] Query Database Error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), " database query error: %s", err.Error()) + } + + var logs []types.TrafficLogDetails + for _, v := range data { + logs = append(logs, types.TrafficLogDetails{ + Id: v.Id, + UserId: v.UserId, + ServerId: v.ServerId, + SubscribeId: v.SubscribeId, + Download: v.Download, + Upload: v.Upload, + Timestamp: v.Timestamp.UnixMilli(), + }) + } + + return &types.FilterTrafficLogDetailsResponse{ + List: logs, + Total: total, + }, nil +} diff --git a/internal/logic/admin/log/filterUserSubscribeTrafficLogLogic.go b/internal/logic/admin/log/filterUserSubscribeTrafficLogLogic.go new file mode 100644 index 0000000..f6c5a46 --- /dev/null +++ b/internal/logic/admin/log/filterUserSubscribeTrafficLogLogic.go @@ -0,0 +1,160 @@ +package log + +import ( + "context" + "time" + + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/internal/model/traffic" + "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 FilterUserSubscribeTrafficLogLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewFilterUserSubscribeTrafficLogLogic Filter user subscribe traffic log +func NewFilterUserSubscribeTrafficLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterUserSubscribeTrafficLogLogic { + return &FilterUserSubscribeTrafficLogLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *FilterUserSubscribeTrafficLogLogic) FilterUserSubscribeTrafficLog(req *types.FilterSubscribeTrafficRequest) (resp *types.FilterSubscribeTrafficResponse, err error) { + if req.Size <= 0 { + req.Size = 10 + } + if req.Page <= 0 { + req.Page = 1 + } + + today := time.Now().Format("2006-01-02") + var list []types.UserSubscribeTrafficLog + var total int64 + + if req.Date == today || req.Date == "" { + now := time.Now() + start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local) + end := start.Add(24 * time.Hour).Add(-time.Nanosecond) + + var userTraffic []types.UserSubscribeTrafficLog + err = l.svcCtx.DB.WithContext(l.ctx). + Model(&traffic.TrafficLog{}). + Select("user_id, subscribe_id, SUM(download + upload) AS total, SUM(download) AS download, SUM(upload) AS upload"). + Where("timestamp BETWEEN ? AND ?", start, end). + Group("user_id, subscribe_id"). + Order("SUM(download + upload) DESC"). + Scan(&userTraffic).Error + if err != nil { + l.Errorw("[FilterUserSubscribeTrafficLog] Query Database Error", logger.Field("error", err.Error())) + return nil, err + } + + for _, v := range userTraffic { + list = append(list, types.UserSubscribeTrafficLog{ + UserId: v.UserId, + SubscribeId: v.SubscribeId, + Upload: v.Upload, + Download: v.Download, + Total: v.Total, + Date: today, + Details: true, + }) + } + todayTotal := len(list) + + startIdx := (req.Page - 1) * req.Size + endIdx := startIdx + req.Size + if startIdx < todayTotal { + if endIdx > todayTotal { + endIdx = todayTotal + } + pageData := list[startIdx:endIdx] + return &types.FilterSubscribeTrafficResponse{ + List: pageData, + Total: int64(todayTotal), + }, nil + } + + need := endIdx - todayTotal + historyPage := (need + req.Size - 1) / req.Size // 算出需要的历史页数 + historyData, historyTotal, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: historyPage, + Size: need, + Type: log.TypeSubscribeTraffic.Uint8(), + }) + + if err != nil { + l.Errorw("[FilterUserSubscribeTrafficLog] Query Database Error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[FilterUserSubscribeTrafficLog] Query Database Error") + } + + for _, datum := range historyData { + var item log.UserTraffic + err = item.Unmarshal([]byte(datum.Content)) + if err != nil { + l.Errorw("[FilterUserSubscribeTrafficLog] Unmarshal Content Error", logger.Field("error", err.Error())) + continue + } + list = append(list, types.UserSubscribeTrafficLog{ + UserId: item.UserId, + SubscribeId: item.SubscribeId, + Upload: item.Upload, + Download: item.Download, + Total: item.Total, + Date: datum.Date, + Details: false, + }) + } + // 返回最终分页数据 + if endIdx > len(list) { + endIdx = len(list) + } + pageData := list[startIdx:endIdx] + + return &types.FilterSubscribeTrafficResponse{ + List: pageData, + Total: int64(todayTotal) + historyTotal, + }, nil + } + var data []*log.SystemLog + data, total, err = l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Type: log.TypeSubscribeTraffic.Uint8(), + Data: req.Date, + }) + if err != nil { + l.Errorw("[FilterUserSubscribeTrafficLog] Query Database Error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[FilterUserSubscribeTrafficLog] Query Database Error") + } + for _, datum := range data { + var item log.UserTraffic + err = item.Unmarshal([]byte(datum.Content)) + if err != nil { + l.Errorw("[FilterUserSubscribeTrafficLog] Unmarshal Content Error", logger.Field("error", err.Error())) + continue + } + list = append(list, types.UserSubscribeTrafficLog{ + UserId: item.UserId, + SubscribeId: item.SubscribeId, + Upload: item.Upload, + Download: item.Download, + Total: item.Total, + Date: datum.Date, + Details: false, + }) + } + return &types.FilterSubscribeTrafficResponse{ + List: list, + Total: total, + }, nil +} diff --git a/internal/logic/admin/log/getLogSettingLogic.go b/internal/logic/admin/log/getLogSettingLogic.go new file mode 100644 index 0000000..568d7e0 --- /dev/null +++ b/internal/logic/admin/log/getLogSettingLogic.go @@ -0,0 +1,37 @@ +package log + +import ( + "context" + + "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" +) + +type GetLogSettingLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// Get log setting +func NewGetLogSettingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetLogSettingLogic { + return &GetLogSettingLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetLogSettingLogic) GetLogSetting() (resp *types.LogSetting, err error) { + configs, err := l.svcCtx.SystemModel.GetLogConfig(l.ctx) + if err != nil { + l.Errorw("[GetLogSetting] Database query error", logger.Field("error", err.Error())) + return nil, err + } + resp = &types.LogSetting{} + // reflect to response + tool.SystemConfigSliceReflectToStruct(configs, resp) + return +} diff --git a/internal/logic/admin/log/getMessageLogListLogic.go b/internal/logic/admin/log/getMessageLogListLogic.go index f8be0cb..252028b 100644 --- a/internal/logic/admin/log/getMessageLogListLogic.go +++ b/internal/logic/admin/log/getMessageLogListLogic.go @@ -3,12 +3,11 @@ package log import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/log" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/log" + "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" ) @@ -28,20 +27,39 @@ func NewGetMessageLogListLogic(ctx context.Context, svcCtx *svc.ServiceContext) } func (l *GetMessageLogListLogic) GetMessageLogList(req *types.GetMessageLogListRequest) (resp *types.GetMessageLogListResponse, err error) { - total, data, err := l.svcCtx.LogModel.FindMessageLogList(l.ctx, req.Page, req.Size, log.MessageLogFilterParams{ - Type: req.Type, - Platform: req.Platform, - To: req.To, - Subject: req.Subject, - Content: req.Content, - Status: req.Status, + + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Type: req.Type, + Search: req.Search, }) + if err != nil { - l.Errorw("[GetMessageLogList] Database Error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[GetMessageLogList] Database Error: %s", err.Error()) + l.Errorf("[GetMessageLogList] failed to filter system log: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to filter system log: %v", err.Error()) } + var list []types.MessageLog - tool.DeepCopy(&list, data) + + for _, datum := range data { + var content log.Message + err = content.Unmarshal([]byte(datum.Content)) + if err != nil { + l.Errorf("[GetMessageLogList] failed to unmarshal content: %v", err.Error()) + continue + } + list = append(list, types.MessageLog{ + Id: datum.Id, + Type: datum.Type, + Platform: content.Platform, + To: content.To, + Subject: content.Subject, + Content: content.Content, + Status: content.Status, + CreatedAt: datum.CreatedAt.UnixMilli(), + }) + } return &types.GetMessageLogListResponse{ Total: total, diff --git a/internal/logic/admin/log/updateLogSettingLogic.go b/internal/logic/admin/log/updateLogSettingLogic.go new file mode 100644 index 0000000..39e5846 --- /dev/null +++ b/internal/logic/admin/log/updateLogSettingLogic.go @@ -0,0 +1,63 @@ +package log + +import ( + "context" + "reflect" + + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/system" + "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" + "gorm.io/gorm" +) + +type UpdateLogSettingLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewUpdateLogSettingLogic Update log setting +func NewUpdateLogSettingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateLogSettingLogic { + return &UpdateLogSettingLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *UpdateLogSettingLogic) UpdateLogSetting(req *types.LogSetting) error { + v := reflect.ValueOf(*req) + // Get the reflection type of the structure + t := v.Type() + err := l.svcCtx.SystemModel.Transaction(l.ctx, func(db *gorm.DB) error { + var err error + for i := 0; i < v.NumField(); i++ { + // Get the field name + fieldName := t.Field(i).Name + // Get the field value to string + fieldValue := tool.ConvertValueToString(v.Field(i)) + // Update the server config + err = db.Model(&system.System{}).Where("`category` = 'log' and `key` = ?", fieldName).Update("value", fieldValue).Error + if err != nil { + break + } + } + return err + }) + if err != nil { + l.Errorw("[UpdateLogSetting] update log setting error", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), " update log setting error: %v", err) + } + + l.svcCtx.Config.Log = config.Log{ + AutoClear: *req.AutoClear, + ClearDays: req.ClearDays, + } + + return nil +} diff --git a/internal/logic/admin/marketing/createBatchSendEmailTaskLogic.go b/internal/logic/admin/marketing/createBatchSendEmailTaskLogic.go new file mode 100644 index 0000000..d2f181b --- /dev/null +++ b/internal/logic/admin/marketing/createBatchSendEmailTaskLogic.go @@ -0,0 +1,170 @@ +package marketing + +import ( + "context" + "strconv" + "strings" + "time" + + "github.com/hibiken/asynq" + "github.com/perfect-panel/server/internal/model/task" + "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" + types2 "github.com/perfect-panel/server/queue/types" + "gorm.io/gorm" +) + +type CreateBatchSendEmailTaskLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewCreateBatchSendEmailTaskLogic Create a batch send email task +func NewCreateBatchSendEmailTaskLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateBatchSendEmailTaskLogic { + return &CreateBatchSendEmailTaskLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} +func (l *CreateBatchSendEmailTaskLogic) CreateBatchSendEmailTask(req *types.CreateBatchSendEmailTaskRequest) (err error) { + tx := l.svcCtx.DB + + var emails []string + + // 通用查询器(含 user JOIN + 注册时间范围过滤) + baseQuery := func() *gorm.DB { + query := tx.Model(&user.AuthMethods{}). + Select("auth_identifier"). + Joins("JOIN user ON user.id = user_auth_methods.user_id"). + Where("auth_type = ?", "email") + + if req.RegisterStartTime != 0 { + query = query.Where("user.created_at >= ?", time.UnixMilli(req.RegisterStartTime)) + } + if req.RegisterEndTime != 0 { + query = query.Where("user.created_at <= ?", time.UnixMilli(req.RegisterEndTime)) + } + return query + } + + var query *gorm.DB + + scope := task.ParseScopeType(req.Scope) + + switch scope { + case task.ScopeAll: + query = baseQuery() + + case task.ScopeActive: + query = baseQuery(). + Joins("JOIN user_subscribe ON user.id = user_subscribe.user_id"). + Where("user_subscribe.status IN ?", []int64{1, 2}) + + case task.ScopeExpired: + query = baseQuery(). + Joins("JOIN user_subscribe ON user.id = user_subscribe.user_id"). + Where("user_subscribe.status = ?", 3) + + case task.ScopeNone: + query = baseQuery(). + Joins("LEFT JOIN user_subscribe ON user.id = user_subscribe.user_id"). + Where("user_subscribe.user_id IS NULL") + default: + + } + if query != nil { + // 执行查询 + err = query.Pluck("auth_identifier", &emails).Error + if err != nil { + l.Errorf("[CreateBatchSendEmailTask] Failed to fetch email addresses: %v", err.Error()) + return xerr.NewErrCode(xerr.DatabaseQueryError) + } + } + + // 邮箱列表为空,返回错误 + if len(emails) == 0 && scope != task.ScopeSkip { + l.Errorf("[CreateBatchSendEmailTask] No email addresses found for the specified scope") + return xerr.NewErrMsg("No email addresses found for the specified scope") + } + + // 邮箱地址去重 + emails = tool.RemoveDuplicateElements(emails...) + + var additionalEmails []string + // 追加额外的邮箱地址(不覆盖) + if req.Additional != "" { + additionalEmails = tool.RemoveDuplicateElements(strings.Split(req.Additional, "\n")...) + } + if len(additionalEmails) == 0 && scope == task.ScopeSkip { + l.Errorf("[CreateBatchSendEmailTask] No additional email addresses provided for skip scope") + return xerr.NewErrMsg("No additional email addresses provided for skip scope") + } + + scheduledAt := time.Now().Add(10 * time.Second) // 默认延迟10秒执行,防止任务创建和执行时间过于接近 + if req.Scheduled != 0 { + scheduledAt = time.Unix(req.Scheduled, 0) + if scheduledAt.Before(time.Now()) { + scheduledAt = time.Now() + } + } + + scopeInfo := task.EmailScope{ + Type: scope.Int8(), + RegisterStartTime: req.RegisterStartTime, + RegisterEndTime: req.RegisterEndTime, + Recipients: emails, + Additional: additionalEmails, + Scheduled: req.Scheduled, + Interval: req.Interval, + Limit: req.Limit, + } + scopeBytes, _ := scopeInfo.Marshal() + + taskContent := task.EmailContent{ + Subject: req.Subject, + Content: req.Content, + } + + contentBytes, _ := taskContent.Marshal() + + var total uint64 + if additionalEmails != nil { + list := append(emails, additionalEmails...) + total = uint64(len(tool.RemoveDuplicateElements(list...))) + } else { + total = uint64(len(emails)) + } + + taskInfo := &task.Task{ + Type: task.TypeEmail, + Scope: string(scopeBytes), + Content: string(contentBytes), + Status: 0, + Errors: "", + Total: total, + Current: 0, + } + + if err = l.svcCtx.DB.Model(&task.Task{}).Create(taskInfo).Error; err != nil { + l.Errorf("[CreateBatchSendEmailTask] Failed to create email task: %v", err.Error()) + return xerr.NewErrCode(xerr.DatabaseInsertError) + } + // create task + l.Infof("[CreateBatchSendEmailTask] Successfully created email task with ID: %d", taskInfo.Id) + + t := asynq.NewTask(types2.ScheduledBatchSendEmail, []byte(strconv.FormatInt(taskInfo.Id, 10))) + info, err := l.svcCtx.Queue.EnqueueContext(l.ctx, t, asynq.ProcessAt(scheduledAt)) + if err != nil { + l.Errorf("[CreateBatchSendEmailTask] Failed to enqueue email task: %v", err.Error()) + return xerr.NewErrCode(xerr.QueueEnqueueError) + } + l.Infof("[CreateBatchSendEmailTask] Successfully enqueued email task with ID: %s, scheduled at: %s", info.ID, scheduledAt.Format(time.DateTime)) + + return nil +} diff --git a/internal/logic/admin/marketing/createQuotaTaskLogic.go b/internal/logic/admin/marketing/createQuotaTaskLogic.go new file mode 100644 index 0000000..606435f --- /dev/null +++ b/internal/logic/admin/marketing/createQuotaTaskLogic.go @@ -0,0 +1,104 @@ +package marketing + +import ( + "context" + "strconv" + "time" + + "github.com/hibiken/asynq" + "github.com/perfect-panel/server/internal/model/task" + "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/xerr" + queueType "github.com/perfect-panel/server/queue/types" + "github.com/pkg/errors" +) + +type CreateQuotaTaskLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewCreateQuotaTaskLogic Create a quota task +func NewCreateQuotaTaskLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateQuotaTaskLogic { + return &CreateQuotaTaskLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *CreateQuotaTaskLogic) CreateQuotaTask(req *types.CreateQuotaTaskRequest) error { + var subs []*user.Subscribe + query := l.svcCtx.DB.WithContext(l.ctx).Model(&user.Subscribe{}) + if len(req.Subscribers) > 0 { + query = query.Where("`subscribe_id` IN ?", req.Subscribers) + } + + if req.IsActive != nil && *req.IsActive { + query = query.Where("`status` IN ?", []int64{0, 1, 2}) // 0: Pending 1: Active 2: Finished + } + if req.StartTime != 0 { + start := time.UnixMilli(req.StartTime) + query = query.Where("`start_time` <= ?", start) + } + if req.EndTime != 0 { + end := time.UnixMilli(req.EndTime) + query = query.Where("`expire_time` >= ?", end) + } + + if err := query.Find(&subs).Error; err != nil { + l.Errorf("[CreateQuotaTask] find subscribers error: %v", err.Error()) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribers error") + } + if len(subs) == 0 { + return errors.Wrapf(xerr.NewErrMsg("No subscribers found"), "no subscribers found") + } + var subIds []int64 + for _, sub := range subs { + subIds = append(subIds, sub.Id) + } + + scopeInfo := task.QuotaScope{ + Subscribers: req.Subscribers, + IsActive: req.IsActive, + StartTime: req.StartTime, + EndTime: req.EndTime, + Objects: subIds, + } + scopeBytes, _ := scopeInfo.Marshal() + contentInfo := task.QuotaContent{ + ResetTraffic: req.ResetTraffic, + Days: req.Days, + GiftType: req.GiftType, + GiftValue: req.GiftValue, + } + contentBytes, _ := contentInfo.Marshal() + // create task + newTask := &task.Task{ + Type: task.TypeQuota, + Status: 0, + Scope: string(scopeBytes), + Content: string(contentBytes), + Total: uint64(len(subIds)), + Current: 0, + Errors: "", + } + + if err := l.svcCtx.DB.WithContext(l.ctx).Model(&task.Task{}).Create(newTask).Error; err != nil { + l.Errorf("[CreateQuotaTask] create task error: %v", err.Error()) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create task error") + } + + // enqueue task + queueTask := asynq.NewTask(queueType.ForthwithQuotaTask, []byte(strconv.FormatInt(newTask.Id, 10))) + if _, err := l.svcCtx.Queue.EnqueueContext(l.ctx, queueTask); err != nil { + l.Errorf("[CreateQuotaTask] enqueue task error: %v", err.Error()) + return errors.Wrapf(xerr.NewErrCode(xerr.QueueEnqueueError), "enqueue task error") + } + logger.Infof("[CreateQuotaTask] Successfully created task with ID: %d", newTask.Id) + return nil +} diff --git a/internal/logic/admin/marketing/getBatchSendEmailTaskListLogic.go b/internal/logic/admin/marketing/getBatchSendEmailTaskListLogic.go new file mode 100644 index 0000000..e3a3c7b --- /dev/null +++ b/internal/logic/admin/marketing/getBatchSendEmailTaskListLogic.go @@ -0,0 +1,89 @@ +package marketing + +import ( + "context" + "strings" + + "github.com/perfect-panel/server/internal/model/task" + "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" +) + +type GetBatchSendEmailTaskListLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewGetBatchSendEmailTaskListLogic Get batch send email task list +func NewGetBatchSendEmailTaskListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetBatchSendEmailTaskListLogic { + return &GetBatchSendEmailTaskListLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetBatchSendEmailTaskListLogic) GetBatchSendEmailTaskList(req *types.GetBatchSendEmailTaskListRequest) (resp *types.GetBatchSendEmailTaskListResponse, err error) { + + var tasks []*task.Task + tx := l.svcCtx.DB.Model(&task.Task{}).Where("`type` = ?", task.TypeEmail) + if req.Status != nil { + tx = tx.Where("status = ?", *req.Status) + } + if req.Scope != nil { + tx = tx.Where("scope = ?", req.Scope) + } + if req.Page == 0 { + req.Page = 1 + } + if req.Size == 0 { + req.Size = 10 + } + err = tx.Offset((req.Page - 1) * req.Size).Limit(req.Size).Order("created_at DESC").Find(&tasks).Error + if err != nil { + l.Errorf("failed to get email tasks: %v", err) + return nil, xerr.NewErrCode(xerr.DatabaseQueryError) + } + + list := make([]types.BatchSendEmailTask, 0) + + for _, t := range tasks { + var scopeInfo task.EmailScope + if err = scopeInfo.Unmarshal([]byte(t.Scope)); err != nil { + l.Errorf("[GetBatchSendEmailTaskList] failed to unmarshal email task scope: %v", err.Error()) + continue + } + var contentInfo task.EmailContent + if err = contentInfo.Unmarshal([]byte(t.Content)); err != nil { + l.Errorf("[GetBatchSendEmailTaskList] failed to unmarshal email task content: %v", err.Error()) + continue + } + + list = append(list, types.BatchSendEmailTask{ + Id: t.Id, + Subject: contentInfo.Subject, + Content: contentInfo.Content, + Recipients: strings.Join(scopeInfo.Recipients, "\n"), + Scope: scopeInfo.Type, + RegisterStartTime: scopeInfo.RegisterStartTime, + RegisterEndTime: scopeInfo.RegisterEndTime, + Additional: strings.Join(scopeInfo.Additional, "\n"), + Scheduled: scopeInfo.Scheduled, + Interval: scopeInfo.Interval, + Limit: scopeInfo.Limit, + Status: uint8(t.Status), + Errors: t.Errors, + Total: t.Total, + Current: t.Current, + CreatedAt: t.CreatedAt.UnixMilli(), + UpdatedAt: t.UpdatedAt.UnixMilli(), + }) + } + + return &types.GetBatchSendEmailTaskListResponse{ + List: list, + }, nil +} diff --git a/internal/logic/admin/marketing/getBatchSendEmailTaskStatusLogic.go b/internal/logic/admin/marketing/getBatchSendEmailTaskStatusLogic.go new file mode 100644 index 0000000..e21a7c5 --- /dev/null +++ b/internal/logic/admin/marketing/getBatchSendEmailTaskStatusLogic.go @@ -0,0 +1,44 @@ +package marketing + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/task" + "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" +) + +type GetBatchSendEmailTaskStatusLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewGetBatchSendEmailTaskStatusLogic Get batch send email task status +func NewGetBatchSendEmailTaskStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetBatchSendEmailTaskStatusLogic { + return &GetBatchSendEmailTaskStatusLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetBatchSendEmailTaskStatusLogic) GetBatchSendEmailTaskStatus(req *types.GetBatchSendEmailTaskStatusRequest) (resp *types.GetBatchSendEmailTaskStatusResponse, err error) { + tx := l.svcCtx.DB + + var taskInfo *task.Task + err = tx.Model(&task.Task{}).Where("id = ?", req.Id).First(&taskInfo).Error + if err != nil { + l.Errorf("failed to get email task status, error: %v", err) + return nil, xerr.NewErrCode(xerr.DatabaseQueryError) + } + + return &types.GetBatchSendEmailTaskStatusResponse{ + Status: uint8(taskInfo.Status), + Total: int64(taskInfo.Total), + Current: int64(taskInfo.Current), + Errors: taskInfo.Errors, + }, nil +} diff --git a/internal/logic/admin/marketing/getPreSendEmailCountLogic.go b/internal/logic/admin/marketing/getPreSendEmailCountLogic.go new file mode 100644 index 0000000..9fbdbe4 --- /dev/null +++ b/internal/logic/admin/marketing/getPreSendEmailCountLogic.go @@ -0,0 +1,93 @@ +package marketing + +import ( + "context" + "time" + + "github.com/perfect-panel/server/internal/model/task" + "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/xerr" + "gorm.io/gorm" +) + +type GetPreSendEmailCountLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewGetPreSendEmailCountLogic Get pre-send email count +func NewGetPreSendEmailCountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPreSendEmailCountLogic { + return &GetPreSendEmailCountLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetPreSendEmailCountLogic) GetPreSendEmailCount(req *types.GetPreSendEmailCountRequest) (resp *types.GetPreSendEmailCountResponse, err error) { + tx := l.svcCtx.DB + var count int64 + // 通用查询器(含 user JOIN + 注册时间范围过滤) + baseQuery := func() *gorm.DB { + query := tx.Model(&user.AuthMethods{}). + Select("auth_identifier"). + Joins("JOIN user ON user.id = user_auth_methods.user_id"). + Where("auth_type = ?", "email") + + if req.RegisterStartTime != 0 { + + registerStartTime := time.UnixMilli(req.RegisterStartTime) + + query = query.Where("user.created_at >= ?", registerStartTime) + } + if req.RegisterEndTime != 0 { + registerEndTime := time.UnixMilli(req.RegisterEndTime) + query = query.Where("user.created_at <= ?", registerEndTime) + } + return query + } + var query *gorm.DB + scope := task.ParseScopeType(req.Scope) + + switch scope { + case task.ScopeAll: + query = baseQuery() + + case task.ScopeActive: + query = baseQuery(). + Joins("JOIN user_subscribe ON user.id = user_subscribe.user_id"). + Where("user_subscribe.status IN ?", []int64{1, 2}) + + case task.ScopeExpired: + query = baseQuery(). + Joins("JOIN user_subscribe ON user.id = user_subscribe.user_id"). + Where("user_subscribe.status = ?", 3) + + case task.ScopeNone: + query = baseQuery(). + Joins("LEFT JOIN user_subscribe ON user.id = user_subscribe.user_id"). + Where("user_subscribe.user_id IS NULL") + case task.ScopeSkip: + // Skip scope does not require a count + query = nil + default: + l.Errorf("[CreateBatchSendEmailTask] Invalid scope: %v", req.Scope) + return nil, xerr.NewErrMsg("Invalid email scope") + + } + + if query != nil { + if err = query.Count(&count).Error; err != nil { + l.Errorf("[GetPreSendEmailCount] Count error: %v", err) + return nil, xerr.NewErrMsg("Failed to count emails") + } + } + + return &types.GetPreSendEmailCountResponse{ + Count: count, + }, nil +} diff --git a/internal/logic/admin/marketing/queryQuotaTaskListLogic.go b/internal/logic/admin/marketing/queryQuotaTaskListLogic.go new file mode 100644 index 0000000..50cfd9f --- /dev/null +++ b/internal/logic/admin/marketing/queryQuotaTaskListLogic.go @@ -0,0 +1,83 @@ +package marketing + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/task" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" +) + +type QueryQuotaTaskListLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewQueryQuotaTaskListLogic Query quota task list +func NewQueryQuotaTaskListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryQuotaTaskListLogic { + return &QueryQuotaTaskListLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QueryQuotaTaskListLogic) QueryQuotaTaskList(req *types.QueryQuotaTaskListRequest) (resp *types.QueryQuotaTaskListResponse, err error) { + var data []*task.Task + var count int64 + query := l.svcCtx.DB.Model(&task.Task{}).Where("`type` = ?", task.TypeQuota) + if req.Page == 0 { + req.Page = 1 + } + if req.Size == 0 { + req.Size = 20 + } + + if req.Status != nil { + query = query.Where("`status` = ?", *req.Status) + } + err = query.Count(&count).Offset((req.Page - 1) * req.Size).Limit(req.Size).Order("created_at DESC").Find(&data).Error + if err != nil { + l.Errorf("[QueryQuotaTaskList] failed to get quota tasks: %v", err) + return nil, err + } + + var list []types.QuotaTask + for _, item := range data { + var scopeInfo task.QuotaScope + if err = scopeInfo.Unmarshal([]byte(item.Scope)); err != nil { + l.Errorf("[QueryQuotaTaskList] failed to unmarshal quota task scope: %v", err.Error()) + continue + } + var contentInfo task.QuotaContent + if err = contentInfo.Unmarshal([]byte(item.Content)); err != nil { + l.Errorf("[QueryQuotaTaskList] failed to unmarshal quota task content: %v", err.Error()) + continue + } + list = append(list, types.QuotaTask{ + Id: item.Id, + Subscribers: scopeInfo.Subscribers, + IsActive: scopeInfo.IsActive, + StartTime: scopeInfo.StartTime, + EndTime: scopeInfo.EndTime, + ResetTraffic: contentInfo.ResetTraffic, + Days: contentInfo.Days, + GiftType: contentInfo.GiftType, + GiftValue: contentInfo.GiftValue, + Objects: scopeInfo.Objects, + Status: uint8(item.Status), + Total: int64(item.Total), + Current: int64(item.Current), + Errors: item.Errors, + CreatedAt: item.CreatedAt.UnixMilli(), + UpdatedAt: item.UpdatedAt.UnixMilli(), + }) + } + + return &types.QueryQuotaTaskListResponse{ + Total: count, + List: list, + }, nil +} diff --git a/internal/logic/admin/marketing/queryQuotaTaskPreCountLogic.go b/internal/logic/admin/marketing/queryQuotaTaskPreCountLogic.go new file mode 100644 index 0000000..21b0cb4 --- /dev/null +++ b/internal/logic/admin/marketing/queryQuotaTaskPreCountLogic.go @@ -0,0 +1,55 @@ +package marketing + +import ( + "context" + "time" + + "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" +) + +type QueryQuotaTaskPreCountLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewQueryQuotaTaskPreCountLogic Query quota task pre-count +func NewQueryQuotaTaskPreCountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryQuotaTaskPreCountLogic { + return &QueryQuotaTaskPreCountLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QueryQuotaTaskPreCountLogic) QueryQuotaTaskPreCount(req *types.QueryQuotaTaskPreCountRequest) (resp *types.QueryQuotaTaskPreCountResponse, err error) { + tx := l.svcCtx.DB.WithContext(l.ctx).Model(&user.Subscribe{}) + var count int64 + + if len(req.Subscribers) > 0 { + tx = tx.Where("`subscribe_id` IN ?", req.Subscribers) + } + + if req.IsActive != nil && *req.IsActive { + tx = tx.Where("`status` IN ?", []int64{0, 1, 2}) // 0: Pending 1: Active 2: Finished + } + if req.StartTime != 0 { + start := time.UnixMilli(req.StartTime) + tx = tx.Where("`start_time` <= ?", start) + } + if req.EndTime != 0 { + end := time.UnixMilli(req.EndTime) + tx = tx.Where("`expire_time` >= ?", end) + } + if err = tx.Count(&count).Error; err != nil { + l.Errorf("[QueryQuotaTaskPreCount] count error: %v", err.Error()) + return nil, err + } + + return &types.QueryQuotaTaskPreCountResponse{ + Count: count, + }, nil +} diff --git a/internal/logic/admin/marketing/queryQuotaTaskStatusLogic.go b/internal/logic/admin/marketing/queryQuotaTaskStatusLogic.go new file mode 100644 index 0000000..70599fe --- /dev/null +++ b/internal/logic/admin/marketing/queryQuotaTaskStatusLogic.go @@ -0,0 +1,42 @@ +package marketing + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/task" + "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 QueryQuotaTaskStatusLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewQueryQuotaTaskStatusLogic Query quota task status +func NewQueryQuotaTaskStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryQuotaTaskStatusLogic { + return &QueryQuotaTaskStatusLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QueryQuotaTaskStatusLogic) QueryQuotaTaskStatus(req *types.QueryQuotaTaskStatusRequest) (resp *types.QueryQuotaTaskStatusResponse, err error) { + var data *task.Task + err = l.svcCtx.DB.Model(&task.Task{}).Where("id = ? AND `type` = ?", req.Id, task.TypeQuota).First(&data).Error + if err != nil { + l.Errorf("[QueryQuotaTaskStatus] failed to get quota task: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), " failed to get quota task: %v", err.Error()) + } + return &types.QueryQuotaTaskStatusResponse{ + Status: uint8(data.Status), + Current: int64(data.Current), + Total: int64(data.Total), + Errors: data.Errors, + }, nil +} diff --git a/internal/logic/admin/marketing/stopBatchSendEmailTaskLogic.go b/internal/logic/admin/marketing/stopBatchSendEmailTaskLogic.go new file mode 100644 index 0000000..da3949f --- /dev/null +++ b/internal/logic/admin/marketing/stopBatchSendEmailTaskLogic.go @@ -0,0 +1,42 @@ +package marketing + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/task" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/email" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/xerr" +) + +type StopBatchSendEmailTaskLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewStopBatchSendEmailTaskLogic Stop a batch send email task +func NewStopBatchSendEmailTaskLogic(ctx context.Context, svcCtx *svc.ServiceContext) *StopBatchSendEmailTaskLogic { + return &StopBatchSendEmailTaskLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *StopBatchSendEmailTaskLogic) StopBatchSendEmailTask(req *types.StopBatchSendEmailTaskRequest) (err error) { + if email.Manager != nil { + email.Manager.RemoveWorker(req.Id) + } else { + logger.Error("[StopBatchSendEmailTaskLogic] email.Manager is nil, cannot stop task") + } + err = l.svcCtx.DB.Model(&task.Task{}).Where("id = ?", req.Id).Update("status", 2).Error + + if err != nil { + l.Errorf("failed to stop email task, error: %v", err) + return xerr.NewErrCode(xerr.DatabaseUpdateError) + } + return +} diff --git a/internal/logic/admin/order/createOrderLogic.go b/internal/logic/admin/order/createOrderLogic.go index cc9036c..32abeaf 100644 --- a/internal/logic/admin/order/createOrderLogic.go +++ b/internal/logic/admin/order/createOrderLogic.go @@ -3,12 +3,12 @@ package order import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/order" + "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" ) diff --git a/internal/logic/admin/order/getOrderListLogic.go b/internal/logic/admin/order/getOrderListLogic.go index e272b15..cdf9da6 100644 --- a/internal/logic/admin/order/getOrderListLogic.go +++ b/internal/logic/admin/order/getOrderListLogic.go @@ -3,11 +3,11 @@ package order import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/order/updateOrderStatusLogic.go b/internal/logic/admin/order/updateOrderStatusLogic.go index c82a8a0..17df64d 100644 --- a/internal/logic/admin/order/updateOrderStatusLogic.go +++ b/internal/logic/admin/order/updateOrderStatusLogic.go @@ -5,14 +5,14 @@ import ( "encoding/json" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - queue "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" + queue "github.com/perfect-panel/server/queue/types" ) type UpdateOrderStatusLogic struct { diff --git a/internal/logic/admin/payment/createPaymentMethodLogic.go b/internal/logic/admin/payment/createPaymentMethodLogic.go index 1a132cf..23cd48d 100644 --- a/internal/logic/admin/payment/createPaymentMethodLogic.go +++ b/internal/logic/admin/payment/createPaymentMethodLogic.go @@ -3,16 +3,20 @@ package payment import ( "context" "encoding/json" + "fmt" - "github.com/perfect-panel/ppanel-server/pkg/random" + "github.com/perfect-panel/server/pkg/payment/stripe" + "gorm.io/gorm" - paymentModel "github.com/perfect-panel/ppanel-server/internal/model/payment" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/payment" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/pkg/random" + + paymentModel "github.com/perfect-panel/server/internal/model/payment" + "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/payment" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) @@ -50,9 +54,42 @@ func (l *CreatePaymentMethodLogic) CreatePaymentMethod(req *types.CreatePaymentM Enable: req.Enable, Token: random.KeyNew(8, 1), } - if err := l.svcCtx.PaymentModel.Insert(l.ctx, paymentMethod); err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert payment method error: %s", err.Error()) + err = l.svcCtx.PaymentModel.Transaction(l.ctx, func(tx *gorm.DB) error { + if req.Platform == "Stripe" { + var cfg paymentModel.StripeConfig + if err = cfg.Unmarshal([]byte(paymentMethod.Config)); err != nil { + l.Errorf("[CreatePaymentMethod] unmarshal stripe config error: %s", err.Error()) + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "unmarshal stripe config error: %s", err.Error()) + } + if cfg.SecretKey == "" { + l.Error("[CreatePaymentMethod] stripe secret key is empty") + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "stripe secret key is empty") + } + + // Create Stripe webhook endpoint + client := stripe.NewClient(stripe.Config{ + SecretKey: cfg.SecretKey, + PublicKey: cfg.PublicKey, + }) + url := fmt.Sprintf("%s/v1/notify/Stripe/%s", req.Domain, paymentMethod.Token) + endpoint, err := client.CreateWebhookEndpoint(url) + if err != nil { + l.Errorw("[CreatePaymentMethod] create stripe webhook endpoint error", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "create stripe webhook endpoint error: %s", err.Error()) + } + cfg.WebhookSecret = endpoint.Secret + content, _ := cfg.Marshal() + paymentMethod.Config = string(content) + } + if err = tx.Model(&paymentModel.Payment{}).Create(paymentMethod).Error; err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert payment method error: %s", err.Error()) + } + return nil + }) + if err != nil { + return nil, err } + resp = &types.PaymentConfig{} tool.DeepCopy(resp, paymentMethod) var configMap map[string]interface{} @@ -64,33 +101,36 @@ func (l *CreatePaymentMethodLogic) CreatePaymentMethod(req *types.CreatePaymentM func parsePaymentPlatformConfig(ctx context.Context, platform payment.Platform, config interface{}) string { data, err := json.Marshal(config) if err != nil { - logger.WithContext(ctx).Errorw("parse payment platform config error", logger.Field("platform", platform), logger.Field("config", config), logger.Field("error", err.Error())) + logger.WithContext(ctx).Errorw("marshal config error", logger.Field("platform", platform), logger.Field("config", config), logger.Field("error", err.Error())) + return "" } + + // 通用处理函数 + handleConfig := func(name string, target interface { + Unmarshal([]byte) error + Marshal() ([]byte, error) + }) string { + if err = target.Unmarshal(data); err != nil { + logger.WithContext(ctx).Errorw("parse "+name+" config error", logger.Field("config", string(data)), logger.Field("error", err.Error())) + return "" + } + content, err := target.Marshal() + if err != nil { + logger.WithContext(ctx).Errorw("marshal "+name+" config error", logger.Field("error", err.Error())) + return "" + } + return string(content) + } + switch platform { case payment.Stripe: - stripe := &paymentModel.StripeConfig{} - if err := stripe.Unmarshal(string(data)); err != nil { - logger.WithContext(ctx).Errorw("parse stripe config error", logger.Field("config", string(data)), logger.Field("error", err.Error())) - } - return stripe.Marshal() + return handleConfig("Stripe", &paymentModel.StripeConfig{}) case payment.AlipayF2F: - alipay := &paymentModel.AlipayF2FConfig{} - if err := alipay.Unmarshal(string(data)); err != nil { - logger.WithContext(ctx).Errorw("parse alipay config error", logger.Field("config", string(data)), logger.Field("error", err.Error())) - } - return alipay.Marshal() + return handleConfig("Alipay", &paymentModel.AlipayF2FConfig{}) case payment.EPay: - epay := &paymentModel.EPayConfig{} - if err := epay.Unmarshal(string(data)); err != nil { - logger.WithContext(ctx).Errorw("parse epay config error", logger.Field("config", string(data)), logger.Field("error", err.Error())) - } - return epay.Marshal() - case payment.Payssion: - payssion := &paymentModel.PayssionConfig{} - if err := payssion.Unmarshal(string(data)); err != nil { - logger.WithContext(ctx).Errorw("parse payssion config error", logger.Field("config", string(data)), logger.Field("error", err.Error())) - } - return payssion.Marshal() + return handleConfig("Epay", &paymentModel.EPayConfig{}) + case payment.CryptoSaaS: + return handleConfig("CryptoSaaS", &paymentModel.CryptoSaaSConfig{}) default: return "" } diff --git a/internal/logic/admin/payment/deletePaymentMethodLogic.go b/internal/logic/admin/payment/deletePaymentMethodLogic.go index 72b4075..2508c58 100644 --- a/internal/logic/admin/payment/deletePaymentMethodLogic.go +++ b/internal/logic/admin/payment/deletePaymentMethodLogic.go @@ -3,10 +3,10 @@ package payment import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/payment/getPaymentMethodListLogic.go b/internal/logic/admin/payment/getPaymentMethodListLogic.go index 2b9cbf5..77c7e40 100644 --- a/internal/logic/admin/payment/getPaymentMethodListLogic.go +++ b/internal/logic/admin/payment/getPaymentMethodListLogic.go @@ -4,13 +4,13 @@ import ( "context" "encoding/json" - paymentPlatform "github.com/perfect-panel/ppanel-server/pkg/payment" + paymentPlatform "github.com/perfect-panel/server/pkg/payment" - "github.com/perfect-panel/ppanel-server/internal/model/payment" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/payment" + "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" ) @@ -55,17 +55,18 @@ func (l *GetPaymentMethodListLogic) GetPaymentMethodList(req *types.GetPaymentMe } } resp.List[i] = types.PaymentMethodDetail{ - Id: v.Id, - Name: v.Name, - Platform: v.Platform, - Icon: v.Icon, - Domain: v.Domain, - Config: config, - FeeMode: v.FeeMode, - FeePercent: v.FeePercent, - FeeAmount: v.FeeAmount, - Enable: *v.Enable, - NotifyURL: notifyUrl, + Id: v.Id, + Name: v.Name, + Platform: v.Platform, + Icon: v.Icon, + Domain: v.Domain, + Config: config, + FeeMode: v.FeeMode, + FeePercent: v.FeePercent, + FeeAmount: v.FeeAmount, + Enable: *v.Enable, + NotifyURL: notifyUrl, + Description: v.Description, } } return diff --git a/internal/logic/admin/payment/getPaymentPlatformLogic.go b/internal/logic/admin/payment/getPaymentPlatformLogic.go index e04cac8..0b87b78 100644 --- a/internal/logic/admin/payment/getPaymentPlatformLogic.go +++ b/internal/logic/admin/payment/getPaymentPlatformLogic.go @@ -3,10 +3,10 @@ package payment import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/payment" + "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/payment" ) type GetPaymentPlatformLogic struct { diff --git a/internal/logic/admin/payment/updatePaymentMethodLogic.go b/internal/logic/admin/payment/updatePaymentMethodLogic.go index 25ff600..7c2dda2 100644 --- a/internal/logic/admin/payment/updatePaymentMethodLogic.go +++ b/internal/logic/admin/payment/updatePaymentMethodLogic.go @@ -4,12 +4,12 @@ import ( "context" "encoding/json" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/payment" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/payment" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) @@ -19,7 +19,7 @@ type UpdatePaymentMethodLogic struct { svcCtx *svc.ServiceContext } -// Update Payment Method +// NewUpdatePaymentMethodLogic Update Payment Method func NewUpdatePaymentMethodLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdatePaymentMethodLogic { return &UpdatePaymentMethodLogic{ Logger: logger.WithContext(ctx), @@ -39,7 +39,7 @@ func (l *UpdatePaymentMethodLogic) UpdatePaymentMethod(req *types.UpdatePaymentM return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find payment method error: %s", err.Error()) } config := parsePaymentPlatformConfig(l.ctx, payment.ParsePlatform(req.Platform), req.Config) - tool.DeepCopy(method, req) + tool.DeepCopy(method, req, tool.CopyWithIgnoreEmpty(false)) method.Config = config if err := l.svcCtx.PaymentModel.Update(l.ctx, method); err != nil { l.Errorw("update payment method error", logger.Field("id", req.Id), logger.Field("error", err.Error())) diff --git a/internal/logic/admin/server/batchDeleteNodeGroupLogic.go b/internal/logic/admin/server/batchDeleteNodeGroupLogic.go deleted file mode 100644 index 83f1201..0000000 --- a/internal/logic/admin/server/batchDeleteNodeGroupLogic.go +++ /dev/null @@ -1,44 +0,0 @@ -package server - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type BatchDeleteNodeGroupLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewBatchDeleteNodeGroupLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BatchDeleteNodeGroupLogic { - return &BatchDeleteNodeGroupLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *BatchDeleteNodeGroupLogic) BatchDeleteNodeGroup(req *types.BatchDeleteNodeGroupRequest) error { - // Check if the group is empty - count, err := l.svcCtx.ServerModel.QueryServerCountByServerGroups(l.ctx, req.Ids) - if err != nil { - l.Errorw("[BatchDeleteNodeGroup] Query Database Error: ", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query server error: %v", err) - } - if count > 0 { - return errors.Wrapf(xerr.NewErrCode(xerr.NodeGroupNotEmpty), "group is not empty") - } - // Delete the group - err = l.svcCtx.ServerModel.BatchDeleteNodeGroup(l.ctx, req.Ids) - if err != nil { - l.Errorw("[BatchDeleteNodeGroup] Delete Database Error: ", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), err.Error()) - } - return nil -} diff --git a/internal/logic/admin/server/batchDeleteNodeLogic.go b/internal/logic/admin/server/batchDeleteNodeLogic.go deleted file mode 100644 index 4cfcb95..0000000 --- a/internal/logic/admin/server/batchDeleteNodeLogic.go +++ /dev/null @@ -1,43 +0,0 @@ -package server - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" -) - -type BatchDeleteNodeLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewBatchDeleteNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BatchDeleteNodeLogic { - return &BatchDeleteNodeLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *BatchDeleteNodeLogic) BatchDeleteNode(req *types.BatchDeleteNodeRequest) error { - err := l.svcCtx.DB.Transaction(func(db *gorm.DB) error { - for _, id := range req.Ids { - err := l.svcCtx.ServerModel.Delete(l.ctx, id) - if err != nil { - return err - } - } - return nil - }) - if err != nil { - l.Errorw("[BatchDeleteNode] Delete Database Error: ", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), err.Error()) - } - return nil -} diff --git a/internal/logic/admin/server/constant.go b/internal/logic/admin/server/constant.go new file mode 100644 index 0000000..ae06fea --- /dev/null +++ b/internal/logic/admin/server/constant.go @@ -0,0 +1,11 @@ +package server + +const ( + ShadowSocks = "shadowsocks" + Vmess = "vmess" + Vless = "vless" + Trojan = "trojan" + AnyTLS = "anytls" + Tuic = "tuic" + Hysteria2 = "hysteria2" +) diff --git a/internal/logic/admin/server/createNodeGroupLogic.go b/internal/logic/admin/server/createNodeGroupLogic.go deleted file mode 100644 index 83a8d71..0000000 --- a/internal/logic/admin/server/createNodeGroupLogic.go +++ /dev/null @@ -1,40 +0,0 @@ -package server - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/model/server" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - - "github.com/pkg/errors" -) - -type CreateNodeGroupLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewCreateNodeGroupLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateNodeGroupLogic { - return &CreateNodeGroupLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *CreateNodeGroupLogic) CreateNodeGroup(req *types.CreateNodeGroupRequest) error { - groupInfo := &server.Group{ - Name: req.Name, - Description: req.Description, - } - err := l.svcCtx.ServerModel.InsertGroup(l.ctx, groupInfo) - if err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), err.Error()) - } - return nil -} diff --git a/internal/logic/admin/server/createNodeLogic.go b/internal/logic/admin/server/createNodeLogic.go index 3e87ce9..f635f85 100644 --- a/internal/logic/admin/server/createNodeLogic.go +++ b/internal/logic/admin/server/createNodeLogic.go @@ -2,18 +2,13 @@ package server import ( "context" - "encoding/json" - "strings" - "time" - "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - queue "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/model/node" + "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" ) @@ -23,6 +18,7 @@ type CreateNodeLogic struct { svcCtx *svc.ServiceContext } +// NewCreateNodeLogic Create Node func NewCreateNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateNodeLogic { return &CreateNodeLogic{ Logger: logger.WithContext(ctx), @@ -32,75 +28,19 @@ func NewCreateNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Create } func (l *CreateNodeLogic) CreateNode(req *types.CreateNodeRequest) error { - config, err := json.Marshal(req.Config) - if err != nil { - return err + data := node.Node{ + Name: req.Name, + Tags: tool.StringSliceToString(req.Tags), + Port: req.Port, + Address: req.Address, + ServerId: req.ServerId, + Protocol: req.Protocol, } - var serverInfo server.Server - tool.DeepCopy(&serverInfo, req) - serverInfo.Config = string(config) - nodeRelay, err := json.Marshal(req.RelayNode) - if err != nil { - l.Errorw("[UpdateNode] Marshal RelayNode Error: ", logger.Field("error", err.Error())) - return err - } - if len(req.Tags) > 0 { - serverInfo.Tags = strings.Join(req.Tags, ",") - } - - serverInfo.LastReportedAt = time.UnixMicro(1218124800) - - serverInfo.City = req.City - serverInfo.Country = req.Country - - serverInfo.RelayNode = string(nodeRelay) - if req.Protocol == "vless" { - var cfg types.Vless - if err := json.Unmarshal(config, &cfg); err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "json.Unmarshal error: %v", err.Error()) - } - if cfg.Security == "reality" && cfg.SecurityConfig.RealityPublicKey == "" { - public, private, err := tool.Curve25519Genkey(false, "") - if err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "generate curve25519 key error") - } - cfg.SecurityConfig.RealityPublicKey = public - cfg.SecurityConfig.RealityPrivateKey = private - cfg.SecurityConfig.RealityShortId = tool.GenerateShortID(private) - } - if cfg.SecurityConfig.RealityServerAddr == "" { - cfg.SecurityConfig.RealityServerAddr = cfg.SecurityConfig.SNI - } - if cfg.SecurityConfig.RealityServerPort == 0 { - cfg.SecurityConfig.RealityServerPort = 443 - } - config, _ = json.Marshal(cfg) - serverInfo.Config = string(config) - } - - err = l.svcCtx.ServerModel.Insert(l.ctx, &serverInfo) + err := l.svcCtx.NodeModel.InsertNode(l.ctx, &data) if err != nil { l.Errorw("[CreateNode] Insert Database Error: ", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create server error: %v", err) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "[CreateNode] Insert Database Error") } - // Marshal the task payload - payload, err := json.Marshal(queue.GetNodeCountry{ - Protocol: serverInfo.Protocol, - ServerAddr: serverInfo.ServerAddr, - }) - if err != nil { - l.Errorw("[GetNodeCountry]: Marshal Error", logger.Field("error", err.Error())) - return errors.Wrap(xerr.NewErrCode(xerr.ERROR), "Failed to marshal task payload") - } - // Create a queue task - task := asynq.NewTask(queue.ForthwithGetCountry, payload) - // Enqueue the task - taskInfo, err := l.svcCtx.Queue.Enqueue(task) - if err != nil { - l.Errorw("[GetNodeCountry]: Enqueue Error", logger.Field("error", err.Error()), logger.Field("payload", string(payload))) - return errors.Wrap(xerr.NewErrCode(xerr.ERROR), "Failed to enqueue task") - } - l.Infow("[GetNodeCountry]: Enqueue Success", logger.Field("taskID", taskInfo.ID), logger.Field("payload", string(payload))) return nil } diff --git a/internal/logic/admin/server/createRuleGroupLogic.go b/internal/logic/admin/server/createRuleGroupLogic.go deleted file mode 100644 index d8bb8bb..0000000 --- a/internal/logic/admin/server/createRuleGroupLogic.go +++ /dev/null @@ -1,69 +0,0 @@ -package server - -import ( - "context" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/rules" - - "github.com/perfect-panel/ppanel-server/internal/model/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type CreateRuleGroupLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Create rule group -func NewCreateRuleGroupLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateRuleGroupLogic { - return &CreateRuleGroupLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} -func parseAndValidateRules(ruleText, ruleName string) ([]string, error) { - var rs []string - ruleArr := strings.Split(ruleText, "\n") - if len(ruleArr) == 0 { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "rules is empty") - } - - for _, s := range ruleArr { - r := rules.NewRule(s, ruleName) - if r == nil { - continue - } - if err := r.Validate(); err != nil { - continue - } - rs = append(rs, r.String()) - } - return rs, nil -} -func (l *CreateRuleGroupLogic) CreateRuleGroup(req *types.CreateRuleGroupRequest) error { - rs, err := parseAndValidateRules(req.Rules, req.Name) - if err != nil { - return err - } - - err = l.svcCtx.ServerModel.InsertRuleGroup(l.ctx, &server.RuleGroup{ - Name: req.Name, - Icon: req.Icon, - Tags: tool.StringSliceToString(req.Tags), - Rules: strings.Join(rs, "\n"), - Enable: req.Enable, - }) - if err != nil { - l.Errorw("[CreateRuleGroup] Insert Database Error: ", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create server rule group error: %v", err) - } - return nil -} diff --git a/internal/logic/admin/server/createServerLogic.go b/internal/logic/admin/server/createServerLogic.go new file mode 100644 index 0000000..2ab1993 --- /dev/null +++ b/internal/logic/admin/server/createServerLogic.go @@ -0,0 +1,110 @@ +package server + +import ( + "context" + "strings" + + "github.com/perfect-panel/server/internal/model/node" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/ip" + "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 CreateServerLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewCreateServerLogic Create Server +func NewCreateServerLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateServerLogic { + return &CreateServerLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *CreateServerLogic) CreateServer(req *types.CreateServerRequest) error { + data := node.Server{ + Name: req.Name, + Country: req.Country, + City: req.City, + Address: req.Address, + Sort: req.Sort, + Protocols: "", + } + protocols := make([]node.Protocol, 0) + for _, item := range req.Protocols { + if item.Type == "" { + return errors.Wrapf(xerr.NewErrCodeMsg(xerr.InvalidParams, "protocols type is empty"), "protocols type is empty") + } + var protocol node.Protocol + tool.DeepCopy(&protocol, item) + + // VLESS Reality Key Generation + if protocol.Type == "vless" { + if protocol.Security == "reality" { + if protocol.RealityPublicKey == "" { + public, private, err := tool.Curve25519Genkey(false, "") + if err != nil { + l.Errorf("[CreateServer] Generate Reality Key Error: %v", err.Error()) + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "generate reality key error: %v", err) + } + protocol.RealityPublicKey = public + protocol.RealityPrivateKey = private + protocol.RealityShortId = tool.GenerateShortID(private) + } + if protocol.RealityServerAddr == "" { + protocol.RealityServerAddr = protocol.SNI + } + if protocol.RealityServerPort == 0 { + protocol.RealityServerPort = 443 + } + } + + } + // ShadowSocks 2022 Key Generation + if protocol.Type == "shadowsocks" { + if strings.Contains(protocol.Cipher, "2022") { + var length int + switch protocol.Cipher { + case "2022-blake3-aes-128-gcm": + length = 16 + default: + length = 32 + } + if len(protocol.ServerKey) != length { + protocol.ServerKey = tool.GenerateCipher(protocol.ServerKey, length) + } + } + } + protocols = append(protocols, protocol) + } + + err := data.MarshalProtocols(protocols) + if err != nil { + l.Errorf("[CreateServer] Marshal Protocols Error: %v", err.Error()) + return errors.Wrapf(xerr.NewErrCodeMsg(xerr.InvalidParams, "protocols marshal error"), "protocols marshal error: %v", err) + } + if data.City == "" && data.Country == "" { + // query server ip location + result, err := ip.GetRegionByIp(req.Address) + if err != nil { + l.Errorf("[CreateServer] GetRegionByIp Error: %v", err.Error()) + } else { + data.City = result.City + data.Country = result.Country + } + } + err = l.svcCtx.NodeModel.InsertServer(l.ctx, &data) + if err != nil { + l.Errorf("[CreateServer] Insert Server error: %v", err.Error()) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert server error: %v", err) + } + return nil +} diff --git a/internal/logic/admin/server/deleteNodeGroupLogic.go b/internal/logic/admin/server/deleteNodeGroupLogic.go deleted file mode 100644 index dc1bac3..0000000 --- a/internal/logic/admin/server/deleteNodeGroupLogic.go +++ /dev/null @@ -1,44 +0,0 @@ -package server - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type DeleteNodeGroupLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewDeleteNodeGroupLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteNodeGroupLogic { - return &DeleteNodeGroupLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *DeleteNodeGroupLogic) DeleteNodeGroup(req *types.DeleteNodeGroupRequest) error { - // Check if the group is empty - count, err := l.svcCtx.ServerModel.QueryServerCountByServerGroups(l.ctx, []int64{req.Id}) - if err != nil { - l.Errorw("[DeleteNodeGroup] Query Database Error: ", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query server error: %v", err) - } - if count > 0 { - return errors.Wrapf(xerr.NewErrCode(xerr.NodeGroupNotEmpty), "group is not empty") - } - // Delete the group - err = l.svcCtx.ServerModel.DeleteGroup(l.ctx, req.Id) - if err != nil { - l.Errorw("[DeleteNodeGroup] Delete Database Error: ", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), err.Error()) - } - return nil -} diff --git a/internal/logic/admin/server/deleteNodeLogic.go b/internal/logic/admin/server/deleteNodeLogic.go index 5c5655c..8a5f6a0 100644 --- a/internal/logic/admin/server/deleteNodeLogic.go +++ b/internal/logic/admin/server/deleteNodeLogic.go @@ -2,14 +2,14 @@ package server import ( "context" + "strings" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/node" + "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" - "gorm.io/gorm" ) type DeleteNodeLogic struct { @@ -18,6 +18,7 @@ type DeleteNodeLogic struct { svcCtx *svc.ServiceContext } +// NewDeleteNodeLogic Delete Node func NewDeleteNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteNodeLogic { return &DeleteNodeLogic{ Logger: logger.WithContext(ctx), @@ -27,30 +28,20 @@ func NewDeleteNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Delete } func (l *DeleteNodeLogic) DeleteNode(req *types.DeleteNodeRequest) error { - err := l.svcCtx.DB.Transaction(func(tx *gorm.DB) error { - // Delete server - err := l.svcCtx.ServerModel.Delete(l.ctx, req.Id) - if err != nil { - return err - } - // Delete server to subscribe - subs, err := l.svcCtx.SubscribeModel.QuerySubscribeIdsByServerIdAndServerGroupId(l.ctx, req.Id, 0) - if err != nil { - return err - } - for _, sub := range subs { - servers := tool.StringToInt64Slice(sub.Server) - newServers := tool.RemoveElementBySlice(servers, req.Id) - sub.Server = tool.Int64SliceToString(newServers) - if err = l.svcCtx.SubscribeModel.Update(l.ctx, sub); err != nil { - return err - } - } - return nil - }) + data, err := l.svcCtx.NodeModel.FindOneNode(l.ctx, req.Id) + + err = l.svcCtx.NodeModel.DeleteNode(l.ctx, req.Id) if err != nil { l.Errorw("[DeleteNode] Delete Database Error: ", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete server error: %v", err) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "[DeleteNode] Delete Database Error") } - return nil + + return l.svcCtx.NodeModel.ClearNodeCache(l.ctx, &node.FilterNodeParams{ + Page: 1, + Size: 1000, + ServerId: []int64{data.ServerId}, + Tag: strings.Split(data.Tags, ","), + Search: "", + Protocol: data.Protocol, + }) } diff --git a/internal/logic/admin/server/deleteRuleGroupLogic.go b/internal/logic/admin/server/deleteRuleGroupLogic.go deleted file mode 100644 index 63c7acb..0000000 --- a/internal/logic/admin/server/deleteRuleGroupLogic.go +++ /dev/null @@ -1,35 +0,0 @@ -package server - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type DeleteRuleGroupLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Delete rule group -func NewDeleteRuleGroupLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteRuleGroupLogic { - return &DeleteRuleGroupLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *DeleteRuleGroupLogic) DeleteRuleGroup(req *types.DeleteRuleGroupRequest) error { - err := l.svcCtx.ServerModel.DeleteRuleGroup(l.ctx, req.Id) - if err != nil { - l.Errorw("[DeleteRuleGroup] Delete Database Error: ", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete server rule group error: %v", err) - } - return nil -} diff --git a/internal/logic/admin/server/deleteServerLogic.go b/internal/logic/admin/server/deleteServerLogic.go new file mode 100644 index 0000000..186d14a --- /dev/null +++ b/internal/logic/admin/server/deleteServerLogic.go @@ -0,0 +1,41 @@ +package server + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/node" + "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 DeleteServerLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewDeleteServerLogic Delete Server +func NewDeleteServerLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteServerLogic { + return &DeleteServerLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *DeleteServerLogic) DeleteServer(req *types.DeleteServerRequest) error { + err := l.svcCtx.NodeModel.DeleteServer(l.ctx, req.Id) + if err != nil { + l.Errorw("[DeleteServer] Delete Server Error: ", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "[DeleteServer] Delete Server Error") + } + return l.svcCtx.NodeModel.ClearNodeCache(l.ctx, &node.FilterNodeParams{ + Page: 1, + Size: 1000, + ServerId: []int64{req.Id}, + Search: "", + }) +} diff --git a/internal/logic/admin/server/filterNodeListLogic.go b/internal/logic/admin/server/filterNodeListLogic.go new file mode 100644 index 0000000..2e41cec --- /dev/null +++ b/internal/logic/admin/server/filterNodeListLogic.go @@ -0,0 +1,64 @@ +package server + +import ( + "context" + "strings" + + "github.com/perfect-panel/server/internal/model/node" + "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 FilterNodeListLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewFilterNodeListLogic Filter Node List +func NewFilterNodeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterNodeListLogic { + return &FilterNodeListLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *FilterNodeListLogic) FilterNodeList(req *types.FilterNodeListRequest) (resp *types.FilterNodeListResponse, err error) { + total, data, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{ + Page: req.Page, + Size: req.Size, + Search: req.Search, + }) + + if err != nil { + l.Errorw("[FilterNodeList] Query Database Error: ", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[FilterNodeList] Query Database Error") + } + + list := make([]types.Node, 0) + for _, datum := range data { + list = append(list, types.Node{ + Id: datum.Id, + Name: datum.Name, + Tags: tool.RemoveDuplicateElements(strings.Split(datum.Tags, ",")...), + Port: datum.Port, + Address: datum.Address, + ServerId: datum.ServerId, + Protocol: datum.Protocol, + Enabled: datum.Enabled, + Sort: datum.Sort, + CreatedAt: datum.CreatedAt.UnixMilli(), + UpdatedAt: datum.UpdatedAt.UnixMilli(), + }) + } + + return &types.FilterNodeListResponse{ + List: list, + Total: total, + }, nil +} diff --git a/internal/logic/admin/server/filterServerListLogic.go b/internal/logic/admin/server/filterServerListLogic.go new file mode 100644 index 0000000..fb47ffa --- /dev/null +++ b/internal/logic/admin/server/filterServerListLogic.go @@ -0,0 +1,164 @@ +package server + +import ( + "context" + "time" + + "github.com/perfect-panel/server/internal/model/node" + "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" + "github.com/redis/go-redis/v9" + "gorm.io/gorm" +) + +type FilterServerListLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewFilterServerListLogic Filter Server List +func NewFilterServerListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterServerListLogic { + return &FilterServerListLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *FilterServerListLogic) FilterServerList(req *types.FilterServerListRequest) (resp *types.FilterServerListResponse, err error) { + total, data, err := l.svcCtx.NodeModel.FilterServerList(l.ctx, &node.FilterParams{ + Page: req.Page, + Size: req.Size, + Search: req.Search, + }) + if err != nil { + l.Errorw("[FilterServerList] Query Database Error: ", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[FilterServerList] Query Database Error") + } + + list := make([]types.Server, 0) + + for _, datum := range data { + var server types.Server + tool.DeepCopy(&server, datum) + + // handler protocols + var protocols []types.Protocol + dst, err := datum.UnmarshalProtocols() + if err != nil { + l.Errorf("[FilterServerList] UnmarshalProtocols Error: %s", err.Error()) + continue + } + tool.DeepCopy(&protocols, dst) + server.Protocols = protocols + + nodeStatus, err := l.svcCtx.NodeModel.StatusCache(l.ctx, datum.Id) + if err != nil { + if !errors.Is(err, redis.Nil) { + l.Errorw("[handlerServerStatus] GetNodeStatus Error: ", logger.Field("error", err.Error()), logger.Field("node_id", datum.Id)) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetNodeStatus Error") + } + server.Status = types.ServerStatus{ + Mem: nodeStatus.Mem, + Cpu: nodeStatus.Cpu, + Disk: nodeStatus.Disk, + Online: l.handlerServerStatus(datum.Id, protocols), + Status: l.handlerServerStaus(datum.LastReportedAt), + } + list = append(list, server) + } + + return &types.FilterServerListResponse{ + List: list, + Total: total, + }, nil +} + +func (l *FilterServerListLogic) handlerServerStatus(id int64, protocols []types.Protocol) []types.ServerOnlineUser { + result := make([]types.ServerOnlineUser, 0) + + for _, protocol := range protocols { + // query online user + data, err := l.svcCtx.NodeModel.OnlineUserSubscribe(l.ctx, id, protocol.Type) + if err != nil { + if !errors.Is(err, redis.Nil) { + l.Errorw("[handlerServerStatus] OnlineUserSubscribe Error: ", logger.Field("error", err.Error()), logger.Field("node_id", id), logger.Field("protocol", protocol.Type)) + } + continue + } + if len(data) > 0 { + for sub, online := range data { + var ips []types.ServerOnlineIP + for _, ip := range online { + ips = append(ips, types.ServerOnlineIP{ + IP: ip, + Protocol: protocol.Type, + }) + } + + result = append(result, types.ServerOnlineUser{ + IP: ips, + SubscribeId: sub, + }) + } + } + } + // merge same subscribe + var mapResult = make(map[int64]types.ServerOnlineUser) + for _, item := range result { + if exist, ok := mapResult[item.SubscribeId]; ok { + // merge + exist.Traffic += item.Traffic + exist.IP = append(exist.IP, item.IP...) + mapResult[item.SubscribeId] = exist + } else { + // get subscribe info + info, err := l.svcCtx.UserModel.FindOneUserSubscribe(l.ctx, item.SubscribeId) + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + l.Errorw("[handlerServerStatus] FindOneSubscribe Error: ", logger.Field("error", err.Error()), logger.Field("subscribe_id", item.SubscribeId)) + } + continue + } + data := types.ServerOnlineUser{ + IP: item.IP, + UserId: info.UserId, + Subscribe: "", + SubscribeId: item.SubscribeId, + Traffic: info.Download + info.Upload, + ExpiredAt: info.ExpireTime.UnixMilli(), + } + if info.Subscribe != nil { + data.Subscribe = info.Subscribe.Name + } + // add new + mapResult[item.SubscribeId] = data + } + } + // convert map to slice + result = make([]types.ServerOnlineUser, 0, len(mapResult)) + for _, item := range mapResult { + result = append(result, item) + } + return result +} + +func (l *FilterServerListLogic) handlerServerStaus(last *time.Time) string { + if last == nil { + return "offline" + } + if time.Since(*last) > time.Minute*5 { + return "offline" + } + if time.Since(*last) > time.Minute*3 { + return "warning" + } + return "online" + +} diff --git a/internal/logic/admin/server/getNodeDetailLogic.go b/internal/logic/admin/server/getNodeDetailLogic.go deleted file mode 100644 index c71d0be..0000000 --- a/internal/logic/admin/server/getNodeDetailLogic.go +++ /dev/null @@ -1,43 +0,0 @@ -package server - -import ( - "context" - "encoding/json" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type GetNodeDetailLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewGetNodeDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetNodeDetailLogic { - return &GetNodeDetailLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *GetNodeDetailLogic) GetNodeDetail(req *types.GetDetailRequest) (resp *types.Server, err error) { - detail, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.Id) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get server detail error: %v", err.Error()) - } - resp = &types.Server{} - tool.DeepCopy(resp, detail) - var cfg map[string]interface{} - err = json.Unmarshal([]byte(detail.Config), &cfg) - if err != nil { - cfg = make(map[string]interface{}) - } - resp.Config = cfg - return -} diff --git a/internal/logic/admin/server/getNodeGroupListLogic.go b/internal/logic/admin/server/getNodeGroupListLogic.go deleted file mode 100644 index 4a5cc32..0000000 --- a/internal/logic/admin/server/getNodeGroupListLogic.go +++ /dev/null @@ -1,39 +0,0 @@ -package server - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type GetNodeGroupListLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewGetNodeGroupListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetNodeGroupListLogic { - return &GetNodeGroupListLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *GetNodeGroupListLogic) GetNodeGroupList() (resp *types.GetNodeGroupListResponse, err error) { - nodeGroupList, err := l.svcCtx.ServerModel.QueryAllGroup(l.ctx) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), err.Error()) - } - nodeGroups := make([]types.ServerGroup, 0) - tool.DeepCopy(&nodeGroups, nodeGroupList) - return &types.GetNodeGroupListResponse{ - Total: int64(len(nodeGroups)), - List: nodeGroups, - }, nil -} diff --git a/internal/logic/admin/server/getNodeListLogic.go b/internal/logic/admin/server/getNodeListLogic.go deleted file mode 100644 index 1e04be6..0000000 --- a/internal/logic/admin/server/getNodeListLogic.go +++ /dev/null @@ -1,100 +0,0 @@ -package server - -import ( - "context" - "encoding/json" - "strings" - - "github.com/perfect-panel/ppanel-server/internal/model/server" - - "github.com/redis/go-redis/v9" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type GetNodeListLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewGetNodeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetNodeListLogic { - return &GetNodeListLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *GetNodeListLogic) GetNodeList(req *types.GetNodeServerListRequest) (resp *types.GetNodeServerListResponse, err error) { - total, list, err := l.svcCtx.ServerModel.FindServerListByFilter(l.ctx, &server.ServerFilter{ - Page: req.Page, - Size: req.Size, - Search: req.Search, - Tag: req.Tag, - Group: req.GroupId, - }) - if err != nil { - l.Errorw("[GetNodeList] Query Database Error: ", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), err.Error()) - } - nodes := make([]types.Server, 0) - for _, v := range list { - node := types.Server{} - tool.DeepCopy(&node, v) - // default relay mode - if node.RelayMode == "" { - node.RelayMode = "none" - } - if len(v.Tags) > 0 { - if strings.Contains(v.Tags, ",") { - node.Tags = strings.Split(v.Tags, ",") - } else { - node.Tags = []string{v.Tags} - } - } - // parse config - var cfg map[string]interface{} - err = json.Unmarshal([]byte(v.Config), &cfg) - if err != nil { - cfg = make(map[string]interface{}) - } - node.Config = cfg - relayNode := make([]types.NodeRelay, 0) - err = json.Unmarshal([]byte(v.RelayNode), &relayNode) - if err != nil { - l.Errorw("[GetNodeList] Unmarshal RelayNode Error: ", logger.Field("error", err.Error()), logger.Field("relayNode", v.RelayNode)) - } - node.RelayNode = relayNode - var status types.NodeStatus - nodeStatus, err := l.svcCtx.NodeCache.GetNodeStatus(l.ctx, v.Id) - if err != nil { - // redis nil is not a Error - if !errors.Is(err, redis.Nil) { - l.Errorw("[GetNodeList] Get Node Status Error: ", logger.Field("error", err.Error())) - } - } else { - onlineUser, err := l.svcCtx.NodeCache.GetNodeOnlineUser(l.ctx, v.Id) - if err != nil { - l.Errorw("[GetNodeList] Get Node Online User Error: ", logger.Field("error", err.Error())) - } else { - status.Online = onlineUser - } - status.Cpu = nodeStatus.Cpu - status.Mem = nodeStatus.Mem - status.Disk = nodeStatus.Disk - status.UpdatedAt = nodeStatus.UpdatedAt - } - node.Status = &status - nodes = append(nodes, node) - } - return &types.GetNodeServerListResponse{ - Total: total, - List: nodes, - }, nil -} diff --git a/internal/logic/admin/server/getNodeTagListLogic.go b/internal/logic/admin/server/getNodeTagListLogic.go deleted file mode 100644 index 722fd6d..0000000 --- a/internal/logic/admin/server/getNodeTagListLogic.go +++ /dev/null @@ -1,53 +0,0 @@ -package server - -import ( - "context" - "strings" - - "github.com/perfect-panel/ppanel-server/internal/model/server" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" -) - -type GetNodeTagListLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get node tag list -func NewGetNodeTagListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetNodeTagListLogic { - return &GetNodeTagListLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *GetNodeTagListLogic) GetNodeTagList() (resp *types.GetNodeTagListResponse, err error) { - var nodeTags, tags []string - err = l.svcCtx.ServerModel.Transaction(l.ctx, func(db *gorm.DB) error { - - return db.Model(&server.Server{}).Select("tags").Pluck("tags", &nodeTags).Error - }) - - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get node tag list failed, %s", err.Error()) - } - - for _, tag := range nodeTags { - tags = append(tags, strings.Split(tag, ",")...) - } - - // Remove duplicate tags - tags = tool.RemoveDuplicateElements(tags...) - - return &types.GetNodeTagListResponse{ - Tags: tags, - }, nil -} diff --git a/internal/logic/admin/server/getRuleGroupListLogic.go b/internal/logic/admin/server/getRuleGroupListLogic.go deleted file mode 100644 index a22766d..0000000 --- a/internal/logic/admin/server/getRuleGroupListLogic.go +++ /dev/null @@ -1,52 +0,0 @@ -package server - -import ( - "context" - "strings" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type GetRuleGroupListLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get rule group list -func NewGetRuleGroupListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetRuleGroupListLogic { - return &GetRuleGroupListLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *GetRuleGroupListLogic) GetRuleGroupList() (resp *types.GetRuleGroupResponse, err error) { - nodeRuleGroupList, err := l.svcCtx.ServerModel.QueryAllRuleGroup(l.ctx) - if err != nil { - l.Errorw("[GetRuleGroupList] Query Database Error: ", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), err.Error()) - } - nodeRuleGroups := make([]types.ServerRuleGroup, len(nodeRuleGroupList)) - for i, v := range nodeRuleGroupList { - nodeRuleGroups[i] = types.ServerRuleGroup{ - Id: v.Id, - Icon: v.Icon, - Name: v.Name, - Tags: strings.Split(v.Tags, ","), - Rules: v.Rules, - Enable: v.Enable, - CreatedAt: v.CreatedAt.UnixMilli(), - UpdatedAt: v.UpdatedAt.UnixMilli(), - } - } - return &types.GetRuleGroupResponse{ - Total: int64(len(nodeRuleGroups)), - List: nodeRuleGroups, - }, nil -} diff --git a/internal/logic/admin/server/getServerProtocolsLogic.go b/internal/logic/admin/server/getServerProtocolsLogic.go new file mode 100644 index 0000000..66519d9 --- /dev/null +++ b/internal/logic/admin/server/getServerProtocolsLogic.go @@ -0,0 +1,49 @@ +package server + +import ( + "context" + + "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 GetServerProtocolsLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// Get Server Protocols +func NewGetServerProtocolsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetServerProtocolsLogic { + return &GetServerProtocolsLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetServerProtocolsLogic) GetServerProtocols(req *types.GetServerProtocolsRequest) (resp *types.GetServerProtocolsResponse, err error) { + // find server + data, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.Id) + if err != nil { + l.Errorf("[GetServerProtocols] FindOneServer Error: %s", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[GetServerProtocols] FindOneServer Error: %s", err.Error()) + } + + // handler protocols + var protocols []types.Protocol + dst, err := data.UnmarshalProtocols() + if err != nil { + l.Errorf("[FilterServerList] UnmarshalProtocols Error: %s", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[FilterServerList] UnmarshalProtocols Error: %s", err.Error()) + } + tool.DeepCopy(&protocols, dst) + + return &types.GetServerProtocolsResponse{ + Protocols: protocols, + }, nil +} diff --git a/internal/logic/admin/server/hasMigrateSeverNodeLogic.go b/internal/logic/admin/server/hasMigrateSeverNodeLogic.go new file mode 100644 index 0000000..128b7f6 --- /dev/null +++ b/internal/logic/admin/server/hasMigrateSeverNodeLogic.go @@ -0,0 +1,52 @@ +package server + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/node" + "github.com/perfect-panel/server/internal/model/server" + "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 HasMigrateSeverNodeLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewHasMigrateSeverNodeLogic Check if there is any server or node to migrate +func NewHasMigrateSeverNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *HasMigrateSeverNodeLogic { + return &HasMigrateSeverNodeLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *HasMigrateSeverNodeLogic) HasMigrateSeverNode() (resp *types.HasMigrateSeverNodeResponse, err error) { + var oldCount, newCount int64 + query := l.svcCtx.DB.WithContext(l.ctx) + + err = query.Model(&server.Server{}).Count(&oldCount).Error + if err != nil { + l.Errorw("[HasMigrateSeverNode] Query Old Server Count Error: ", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[HasMigrateSeverNode] Query Old Server Count Error") + } + err = query.Model(&node.Server{}).Count(&newCount).Error + if err != nil { + l.Errorw("[HasMigrateSeverNode] Query New Server Count Error: ", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[HasMigrateSeverNode] Query New Server Count Error") + } + var shouldMigrate bool + if oldCount != 0 && newCount == 0 { + shouldMigrate = true + } + + return &types.HasMigrateSeverNodeResponse{ + HasMigrate: shouldMigrate, + }, nil +} diff --git a/internal/logic/admin/server/migrateServerNodeLogic.go b/internal/logic/admin/server/migrateServerNodeLogic.go new file mode 100644 index 0000000..4f0a497 --- /dev/null +++ b/internal/logic/admin/server/migrateServerNodeLogic.go @@ -0,0 +1,338 @@ +package server + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/perfect-panel/server/internal/model/node" + "github.com/perfect-panel/server/internal/model/server" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" +) + +type MigrateServerNodeLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewMigrateServerNodeLogic Migrate server and node data to new database +func NewMigrateServerNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MigrateServerNodeLogic { + return &MigrateServerNodeLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *MigrateServerNodeLogic) MigrateServerNode() (resp *types.MigrateServerNodeResponse, err error) { + tx := l.svcCtx.DB.WithContext(l.ctx).Begin() + var oldServers []*server.Server + var newServers []*node.Server + var newNodes []*node.Node + + err = tx.Model(&server.Server{}).Find(&oldServers).Error + if err != nil { + l.Errorw("[MigrateServerNode] Query Old Server List Error: ", logger.Field("error", err.Error())) + return &types.MigrateServerNodeResponse{ + Succee: 0, + Fail: 0, + Message: fmt.Sprintf("Query Old Server List Error: %s", err.Error()), + }, nil + } + for _, oldServer := range oldServers { + data, err := l.adapterServer(oldServer) + if err != nil { + l.Errorw("[MigrateServerNode] Adapter Server Error: ", logger.Field("error", err.Error())) + if resp == nil { + resp = &types.MigrateServerNodeResponse{} + } + resp.Fail++ + if resp.Message == "" { + resp.Message = fmt.Sprintf("Adapter Server Error: %s", err.Error()) + } else { + resp.Message = fmt.Sprintf("%s; Adapter Server Error: %s", resp.Message, err.Error()) + } + continue + } + newServers = append(newServers, data) + + newNode, err := l.adapterNode(oldServer) + if err != nil { + l.Errorw("[MigrateServerNode] Adapter Node Error: ", logger.Field("error", err.Error())) + if resp == nil { + resp = &types.MigrateServerNodeResponse{} + } + resp.Fail++ + if resp.Message == "" { + resp.Message = fmt.Sprintf("Adapter Node Error: %s", err.Error()) + } else { + resp.Message = fmt.Sprintf("%s; Adapter Node Error: %s", resp.Message, err.Error()) + } + continue + } + for _, item := range newNode { + if item.Port == 0 { + protocols, _ := data.UnmarshalProtocols() + if len(protocols) > 0 { + item.Port = protocols[0].Port + } + } + newNodes = append(newNodes, item) + } + } + + if len(newServers) > 0 { + err = tx.Model(&node.Server{}).CreateInBatches(newServers, 20).Error + if err != nil { + tx.Rollback() + l.Errorw("[MigrateServerNode] Insert New Server List Error: ", logger.Field("error", err.Error())) + return &types.MigrateServerNodeResponse{ + Succee: 0, + Fail: uint64(len(newServers)), + Message: fmt.Sprintf("Insert New Server List Error: %s", err.Error()), + }, nil + } + } + if len(newNodes) > 0 { + err = tx.Model(&node.Node{}).CreateInBatches(newNodes, 20).Error + if err != nil { + tx.Rollback() + l.Errorw("[MigrateServerNode] Insert New Node List Error: ", logger.Field("error", err.Error())) + return &types.MigrateServerNodeResponse{ + Succee: uint64(len(newServers)), + Fail: uint64(len(newNodes)), + Message: fmt.Sprintf("Insert New Node List Error: %s", err.Error()), + }, nil + } + } + tx.Commit() + + return &types.MigrateServerNodeResponse{ + Succee: uint64(len(newServers)), + Fail: 0, + Message: fmt.Sprintf("Migrate Success: %d servers and %d nodes", len(newServers), len(newNodes)), + }, nil +} + +func (l *MigrateServerNodeLogic) adapterServer(info *server.Server) (*node.Server, error) { + result := &node.Server{ + Id: info.Id, + Name: info.Name, + Country: info.Country, + City: info.City, + //Ratio: info.TrafficRatio, + Address: info.ServerAddr, + Sort: int(info.Sort), + Protocols: "", + } + var protocols []node.Protocol + + switch info.Protocol { + case ShadowSocks: + var src server.Shadowsocks + err := json.Unmarshal([]byte(info.Config), &src) + if err != nil { + return nil, err + } + protocols = append(protocols, node.Protocol{ + Type: "shadowsocks", + Cipher: src.Method, + Port: uint16(src.Port), + ServerKey: src.ServerKey, + Ratio: float64(info.TrafficRatio), + }) + case Vmess: + var src server.Vmess + err := json.Unmarshal([]byte(info.Config), &src) + if err != nil { + return nil, err + } + protocol := node.Protocol{ + Type: "vmess", + Port: uint16(src.Port), + Security: src.Security, + SNI: src.SecurityConfig.SNI, + AllowInsecure: src.SecurityConfig.AllowInsecure, + Fingerprint: src.SecurityConfig.Fingerprint, + RealityServerAddr: src.SecurityConfig.RealityServerAddr, + RealityServerPort: src.SecurityConfig.RealityServerPort, + RealityPrivateKey: src.SecurityConfig.RealityPrivateKey, + RealityPublicKey: src.SecurityConfig.RealityPublicKey, + RealityShortId: src.SecurityConfig.RealityShortId, + Transport: src.Transport, + Host: src.TransportConfig.Host, + Path: src.TransportConfig.Path, + ServiceName: src.TransportConfig.ServiceName, + Flow: src.Flow, + Ratio: float64(info.TrafficRatio), + } + protocols = append(protocols, protocol) + protocols = append(protocols, protocol) + case Vless: + var src server.Vless + err := json.Unmarshal([]byte(info.Config), &src) + if err != nil { + return nil, err + } + protocol := node.Protocol{ + Type: "vless", + Port: uint16(src.Port), + Security: src.Security, + SNI: src.SecurityConfig.SNI, + AllowInsecure: src.SecurityConfig.AllowInsecure, + Fingerprint: src.SecurityConfig.Fingerprint, + RealityServerAddr: src.SecurityConfig.RealityServerAddr, + RealityServerPort: src.SecurityConfig.RealityServerPort, + RealityPrivateKey: src.SecurityConfig.RealityPrivateKey, + RealityPublicKey: src.SecurityConfig.RealityPublicKey, + RealityShortId: src.SecurityConfig.RealityShortId, + Transport: src.Transport, + Host: src.TransportConfig.Host, + Path: src.TransportConfig.Path, + ServiceName: src.TransportConfig.ServiceName, + Flow: src.Flow, + Ratio: float64(info.TrafficRatio), + } + protocols = append(protocols, protocol) + case Trojan: + var src server.Trojan + err := json.Unmarshal([]byte(info.Config), &src) + if err != nil { + return nil, err + } + protocol := node.Protocol{ + Type: "trojan", + Port: uint16(src.Port), + Security: src.Security, + SNI: src.SecurityConfig.SNI, + AllowInsecure: src.SecurityConfig.AllowInsecure, + Fingerprint: src.SecurityConfig.Fingerprint, + RealityServerAddr: src.SecurityConfig.RealityServerAddr, + RealityServerPort: src.SecurityConfig.RealityServerPort, + RealityPrivateKey: src.SecurityConfig.RealityPrivateKey, + RealityPublicKey: src.SecurityConfig.RealityPublicKey, + RealityShortId: src.SecurityConfig.RealityShortId, + Transport: src.Transport, + Host: src.TransportConfig.Host, + Path: src.TransportConfig.Path, + ServiceName: src.TransportConfig.ServiceName, + Flow: src.Flow, + Ratio: float64(info.TrafficRatio), + } + protocols = append(protocols, protocol) + case Hysteria2: + var src server.Hysteria2 + err := json.Unmarshal([]byte(info.Config), &src) + if err != nil { + return nil, err + } + protocol := node.Protocol{ + Type: "hysteria", + Port: uint16(src.Port), + HopPorts: src.HopPorts, + HopInterval: src.HopInterval, + ObfsPassword: src.ObfsPassword, + SNI: src.SecurityConfig.SNI, + AllowInsecure: src.SecurityConfig.AllowInsecure, + Fingerprint: src.SecurityConfig.Fingerprint, + RealityServerAddr: src.SecurityConfig.RealityServerAddr, + RealityServerPort: src.SecurityConfig.RealityServerPort, + RealityPrivateKey: src.SecurityConfig.RealityPrivateKey, + RealityPublicKey: src.SecurityConfig.RealityPublicKey, + RealityShortId: src.SecurityConfig.RealityShortId, + Ratio: float64(info.TrafficRatio), + } + protocols = append(protocols, protocol) + case Tuic: + var src server.Tuic + err := json.Unmarshal([]byte(info.Config), &src) + if err != nil { + return nil, err + } + protocol := node.Protocol{ + Type: "tuic", + Port: uint16(src.Port), + DisableSNI: src.DisableSNI, + ReduceRtt: src.ReduceRtt, + UDPRelayMode: src.UDPRelayMode, + CongestionController: src.CongestionController, + SNI: src.SecurityConfig.SNI, + AllowInsecure: src.SecurityConfig.AllowInsecure, + Fingerprint: src.SecurityConfig.Fingerprint, + RealityServerAddr: src.SecurityConfig.RealityServerAddr, + RealityServerPort: src.SecurityConfig.RealityServerPort, + RealityPrivateKey: src.SecurityConfig.RealityPrivateKey, + RealityPublicKey: src.SecurityConfig.RealityPublicKey, + RealityShortId: src.SecurityConfig.RealityShortId, + Ratio: float64(info.TrafficRatio), + } + protocols = append(protocols, protocol) + case AnyTLS: + var src server.AnyTLS + err := json.Unmarshal([]byte(info.Config), &src) + if err != nil { + return nil, err + } + protocol := node.Protocol{ + Type: "anytls", + Port: uint16(src.Port), + SNI: src.SecurityConfig.SNI, + AllowInsecure: src.SecurityConfig.AllowInsecure, + Fingerprint: src.SecurityConfig.Fingerprint, + RealityServerAddr: src.SecurityConfig.RealityServerAddr, + RealityServerPort: src.SecurityConfig.RealityServerPort, + RealityPrivateKey: src.SecurityConfig.RealityPrivateKey, + RealityPublicKey: src.SecurityConfig.RealityPublicKey, + RealityShortId: src.SecurityConfig.RealityShortId, + Ratio: float64(info.TrafficRatio), + } + protocols = append(protocols, protocol) + } + if len(protocols) > 0 { + err := result.MarshalProtocols(protocols) + if err != nil { + return nil, err + } + } + + return result, nil +} + +func (l *MigrateServerNodeLogic) adapterNode(info *server.Server) ([]*node.Node, error) { + var nodes []*node.Node + enable := true + switch info.RelayMode { + case server.RelayModeNone: + nodes = append(nodes, &node.Node{ + Name: info.Name, + Tags: "", + Port: 0, + Address: info.ServerAddr, + ServerId: info.Id, + Protocol: info.Protocol, + Enabled: &enable, + }) + default: + var relays []server.NodeRelay + err := json.Unmarshal([]byte(info.RelayNode), &relays) + if err != nil { + return nil, err + } + for _, relay := range relays { + nodes = append(nodes, &node.Node{ + Name: relay.Prefix + info.Name, + Tags: "", + Port: uint16(relay.Port), + Address: relay.Host, + ServerId: info.Id, + Protocol: info.Protocol, + Enabled: &enable, + }) + } + } + + return nodes, nil +} diff --git a/internal/logic/admin/server/queryNodeTagLogic.go b/internal/logic/admin/server/queryNodeTagLogic.go new file mode 100644 index 0000000..47e0daf --- /dev/null +++ b/internal/logic/admin/server/queryNodeTagLogic.go @@ -0,0 +1,46 @@ +package server + +import ( + "context" + "strings" + + "github.com/perfect-panel/server/internal/model/node" + "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 QueryNodeTagLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewQueryNodeTagLogic Query all node tags +func NewQueryNodeTagLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryNodeTagLogic { + return &QueryNodeTagLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QueryNodeTagLogic) QueryNodeTag() (resp *types.QueryNodeTagResponse, err error) { + + var nodes []*node.Node + if err = l.svcCtx.DB.WithContext(l.ctx).Model(&node.Node{}).Find(&nodes).Error; err != nil { + l.Errorw("[QueryNodeTag] Query Database Error: ", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[QueryNodeTag] Query Database Error") + } + var tags []string + for _, item := range nodes { + tags = append(tags, strings.Split(item.Tags, ",")...) + } + + return &types.QueryNodeTagResponse{ + Tags: tool.RemoveDuplicateElements(tags...), + }, nil +} diff --git a/internal/logic/admin/server/nodeSortLogic.go b/internal/logic/admin/server/resetSortWithNodeLogic.go similarity index 59% rename from internal/logic/admin/server/nodeSortLogic.go rename to internal/logic/admin/server/resetSortWithNodeLogic.go index f6c8842..3866f54 100644 --- a/internal/logic/admin/server/nodeSortLogic.go +++ b/internal/logic/admin/server/resetSortWithNodeLogic.go @@ -3,36 +3,35 @@ package server import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/server" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/node" + "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" "gorm.io/gorm" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" ) -type NodeSortLogic struct { +type ResetSortWithNodeLogic struct { logger.Logger ctx context.Context svcCtx *svc.ServiceContext } -// Node sort -func NewNodeSortLogic(ctx context.Context, svcCtx *svc.ServiceContext) *NodeSortLogic { - return &NodeSortLogic{ +// NewResetSortWithNodeLogic Reset node sort +func NewResetSortWithNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResetSortWithNodeLogic { + return &ResetSortWithNodeLogic{ Logger: logger.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } -func (l *NodeSortLogic) NodeSort(req *types.NodeSortRequest) error { - err := l.svcCtx.ServerModel.Transaction(l.ctx, func(db *gorm.DB) error { +func (l *ResetSortWithNodeLogic) ResetSortWithNode(req *types.ResetSortRequest) error { + err := l.svcCtx.NodeModel.Transaction(l.ctx, func(db *gorm.DB) error { // find all servers id var existingIDs []int64 - db.Model(&server.Server{}).Select("id").Find(&existingIDs) + db.Model(&node.Node{}).Select("id").Find(&existingIDs) // check if the id is valid validIDMap := make(map[int64]bool) for _, id := range existingIDs { @@ -46,12 +45,12 @@ func (l *NodeSortLogic) NodeSort(req *types.NodeSortRequest) error { } } // query all servers - var servers []*server.Server - db.Model(&server.Server{}).Order("sort ASC").Find(&servers) + var servers []*node.Node + db.Model(&node.Node{}).Order("sort ASC").Find(&servers) // create a map of the current sort currentSortMap := make(map[int64]int64) for _, item := range servers { - currentSortMap[item.Id] = item.Sort + currentSortMap[item.Id] = int64(item.Sort) } // new sort map @@ -67,7 +66,12 @@ func (l *NodeSortLogic) NodeSort(req *types.NodeSortRequest) error { } } for _, item := range itemsToUpdate { - if err := db.Model(&server.Server{}).Where("id = ?", item.Id).Update("sort", item.Sort).Error; err != nil { + s, err := l.svcCtx.NodeModel.FindOneNode(l.ctx, item.Id) + if err != nil { + return err + } + s.Sort = int(item.Sort) + if err = l.svcCtx.NodeModel.UpdateNode(l.ctx, s, db); err != nil { l.Errorw("[NodeSort] Update Database Error: ", logger.Field("error", err.Error()), logger.Field("id", item.Id), logger.Field("sort", item.Sort)) return err } diff --git a/internal/logic/admin/server/resetSortWithServerLogic.go b/internal/logic/admin/server/resetSortWithServerLogic.go new file mode 100644 index 0000000..3fbe237 --- /dev/null +++ b/internal/logic/admin/server/resetSortWithServerLogic.go @@ -0,0 +1,86 @@ +package server + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/node" + "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" + "gorm.io/gorm" +) + +type ResetSortWithServerLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewResetSortWithServerLogic Reset server sort +func NewResetSortWithServerLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResetSortWithServerLogic { + return &ResetSortWithServerLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *ResetSortWithServerLogic) ResetSortWithServer(req *types.ResetSortRequest) error { + err := l.svcCtx.NodeModel.Transaction(l.ctx, func(db *gorm.DB) error { + // find all servers id + var existingIDs []int64 + db.Model(&node.Server{}).Select("id").Find(&existingIDs) + // check if the id is valid + validIDMap := make(map[int64]bool) + for _, id := range existingIDs { + validIDMap[id] = true + } + // check if the sort is valid + var validItems []types.SortItem + for _, item := range req.Sort { + if validIDMap[item.Id] { + validItems = append(validItems, item) + } + } + // query all servers + var servers []*node.Server + db.Model(&node.Server{}).Order("sort ASC").Find(&servers) + // create a map of the current sort + currentSortMap := make(map[int64]int64) + for _, item := range servers { + currentSortMap[item.Id] = int64(item.Sort) + } + + // new sort map + newSortMap := make(map[int64]int64) + for _, item := range validItems { + newSortMap[item.Id] = item.Sort + } + + var itemsToUpdate []types.SortItem + for _, item := range validItems { + if oldSort, exists := currentSortMap[item.Id]; exists && oldSort != item.Sort { + itemsToUpdate = append(itemsToUpdate, item) + } + } + for _, item := range itemsToUpdate { + s, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, item.Id) + if err != nil { + return err + } + s.Sort = int(item.Sort) + if err = l.svcCtx.NodeModel.UpdateServer(l.ctx, s, db); err != nil { + l.Errorw("[NodeSort] Update Database Error: ", logger.Field("error", err.Error()), logger.Field("id", item.Id), logger.Field("sort", item.Sort)) + return err + } + } + return nil + }) + if err != nil { + l.Errorw("[NodeSort] Update Database Error: ", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), err.Error()) + } + return nil +} diff --git a/internal/logic/admin/server/toggleNodeStatusLogic.go b/internal/logic/admin/server/toggleNodeStatusLogic.go new file mode 100644 index 0000000..f71048f --- /dev/null +++ b/internal/logic/admin/server/toggleNodeStatusLogic.go @@ -0,0 +1,51 @@ +package server + +import ( + "context" + "strings" + + "github.com/perfect-panel/server/internal/model/node" + "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 ToggleNodeStatusLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewToggleNodeStatusLogic Toggle Node Status +func NewToggleNodeStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ToggleNodeStatusLogic { + return &ToggleNodeStatusLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *ToggleNodeStatusLogic) ToggleNodeStatus(req *types.ToggleNodeStatusRequest) error { + data, err := l.svcCtx.NodeModel.FindOneNode(l.ctx, req.Id) + if err != nil { + l.Errorw("[ToggleNodeStatus] Query Database Error: ", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[ToggleNodeStatus] Query Database Error") + } + data.Enabled = req.Enable + + err = l.svcCtx.NodeModel.UpdateNode(l.ctx, data) + if err != nil { + l.Errorw("[ToggleNodeStatus] Update Database Error: ", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "[ToggleNodeStatus] Update Database Error") + } + + return l.svcCtx.NodeModel.ClearNodeCache(l.ctx, &node.FilterNodeParams{ + Page: 1, + Size: 1000, + ServerId: []int64{data.ServerId}, + Tag: strings.Split(data.Tags, ","), + Search: "", + }) +} diff --git a/internal/logic/admin/server/updateNodeGroupLogic.go b/internal/logic/admin/server/updateNodeGroupLogic.go deleted file mode 100644 index 6774c0b..0000000 --- a/internal/logic/admin/server/updateNodeGroupLogic.go +++ /dev/null @@ -1,40 +0,0 @@ -package server - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type UpdateNodeGroupLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewUpdateNodeGroupLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateNodeGroupLogic { - return &UpdateNodeGroupLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *UpdateNodeGroupLogic) UpdateNodeGroup(req *types.UpdateNodeGroupRequest) error { - // check server group exist - nodeGroup, err := l.svcCtx.ServerModel.FindOneGroup(l.ctx, req.Id) - if err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), err.Error()) - } - nodeGroup.Name = req.Name - nodeGroup.Description = req.Description - err = l.svcCtx.ServerModel.UpdateGroup(l.ctx, nodeGroup) - if err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), err.Error()) - } - return nil -} diff --git a/internal/logic/admin/server/updateNodeLogic.go b/internal/logic/admin/server/updateNodeLogic.go index c5dc4ff..2af8a4d 100644 --- a/internal/logic/admin/server/updateNodeLogic.go +++ b/internal/logic/admin/server/updateNodeLogic.go @@ -2,18 +2,13 @@ package server import ( "context" - "encoding/json" - "strings" - "github.com/perfect-panel/ppanel-server/pkg/device" - - "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - queue "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/model/node" + "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" ) @@ -23,6 +18,7 @@ type UpdateNodeLogic struct { svcCtx *svc.ServiceContext } +// NewUpdateNodeLogic Update Node func NewUpdateNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateNodeLogic { return &UpdateNodeLogic{ Logger: logger.WithContext(ctx), @@ -32,79 +28,27 @@ func NewUpdateNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Update } func (l *UpdateNodeLogic) UpdateNode(req *types.UpdateNodeRequest) error { - // Check server exist - nodeInfo, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.Id) + data, err := l.svcCtx.NodeModel.FindOneNode(l.ctx, req.Id) if err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find server error: %v", err) + l.Errorw("[UpdateNode] Query Database Error: ", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "[UpdateNode] Query Database Error") } - tool.DeepCopy(nodeInfo, req) - config, err := json.Marshal(req.Config) - if err != nil { - return err - } - - nodeInfo.Config = string(config) - nodeRelay, err := json.Marshal(req.RelayNode) - if err != nil { - l.Errorw("[UpdateNode] Marshal RelayNode Error: ", logger.Field("error", err.Error())) - return err - } - - if len(req.Tags) > 0 { - nodeInfo.Tags = strings.Join(req.Tags, ",") - } - - nodeInfo.City = req.City - nodeInfo.Country = req.Country - - nodeInfo.RelayNode = string(nodeRelay) - if req.Protocol == "vless" { - var cfg types.Vless - if err := json.Unmarshal(config, &cfg); err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "json.Unmarshal error: %v", err.Error()) - } - if cfg.Security == "reality" && cfg.SecurityConfig.RealityPublicKey == "" { - public, private, err := tool.Curve25519Genkey(false, "") - if err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "generate curve25519 key error") - } - cfg.SecurityConfig.RealityPublicKey = public - cfg.SecurityConfig.RealityPrivateKey = private - cfg.SecurityConfig.RealityShortId = tool.GenerateShortID(private) - } - if cfg.SecurityConfig.RealityServerAddr == "" { - cfg.SecurityConfig.RealityServerAddr = cfg.SecurityConfig.SNI - } - if cfg.SecurityConfig.RealityServerPort == 0 { - cfg.SecurityConfig.RealityServerPort = 443 - } - config, _ = json.Marshal(cfg) - nodeInfo.Config = string(config) - } - err = l.svcCtx.ServerModel.Update(l.ctx, nodeInfo) + data.Name = req.Name + data.Tags = tool.StringSliceToString(req.Tags) + data.ServerId = req.ServerId + data.Port = req.Port + data.Address = req.Address + data.Protocol = req.Protocol + data.Enabled = req.Enabled + err = l.svcCtx.NodeModel.UpdateNode(l.ctx, data) if err != nil { l.Errorw("[UpdateNode] Update Database Error: ", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create server error: %v", err) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "[UpdateNode] Update Database Error") } - - // Marshal the task payload - payload, err := json.Marshal(queue.GetNodeCountry{ - Protocol: nodeInfo.Protocol, - ServerAddr: nodeInfo.ServerAddr, + return l.svcCtx.NodeModel.ClearNodeCache(l.ctx, &node.FilterNodeParams{ + Page: 1, + Size: 1000, + ServerId: []int64{data.ServerId}, + Search: "", }) - if err != nil { - l.Errorw("[GetNodeCountry]: Marshal Error", logger.Field("error", err.Error())) - return errors.Wrap(xerr.NewErrCode(xerr.ERROR), "Failed to marshal task payload") - } - // Create a queue task - task := asynq.NewTask(queue.ForthwithGetCountry, payload) - // Enqueue the task - taskInfo, err := l.svcCtx.Queue.Enqueue(task) - if err != nil { - l.Errorw("[GetNodeCountry]: Enqueue Error", logger.Field("error", err.Error()), logger.Field("payload", string(payload))) - return errors.Wrap(xerr.NewErrCode(xerr.ERROR), "Failed to enqueue task") - } - l.Infow("[GetNodeCountry]: Enqueue Success", logger.Field("taskID", taskInfo.ID), logger.Field("payload", string(payload))) - l.svcCtx.DeviceManager.Broadcast(device.SubscribeUpdate) - return nil } diff --git a/internal/logic/admin/server/updateRuleGroupLogic.go b/internal/logic/admin/server/updateRuleGroupLogic.go deleted file mode 100644 index 33193e1..0000000 --- a/internal/logic/admin/server/updateRuleGroupLogic.go +++ /dev/null @@ -1,50 +0,0 @@ -package server - -import ( - "context" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/tool" - - "github.com/perfect-panel/ppanel-server/internal/model/server" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -type UpdateRuleGroupLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// NewUpdateRuleGroupLogic Update rule group -func NewUpdateRuleGroupLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateRuleGroupLogic { - return &UpdateRuleGroupLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *UpdateRuleGroupLogic) UpdateRuleGroup(req *types.UpdateRuleGroupRequest) error { - rs, err := parseAndValidateRules(req.Rules, req.Name) - if err != nil { - return err - } - err = l.svcCtx.ServerModel.UpdateRuleGroup(l.ctx, &server.RuleGroup{ - Id: req.Id, - Icon: req.Icon, - Name: req.Name, - Tags: tool.StringSliceToString(req.Tags), - Rules: strings.Join(rs, "\n"), - Enable: req.Enable, - }) - if err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), err.Error()) - } - return nil -} diff --git a/internal/logic/admin/server/updateServerLogic.go b/internal/logic/admin/server/updateServerLogic.go new file mode 100644 index 0000000..f419130 --- /dev/null +++ b/internal/logic/admin/server/updateServerLogic.go @@ -0,0 +1,119 @@ +package server + +import ( + "context" + "strings" + + "github.com/perfect-panel/server/internal/model/node" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/ip" + "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 UpdateServerLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewUpdateServerLogic Update Server +func NewUpdateServerLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateServerLogic { + return &UpdateServerLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *UpdateServerLogic) UpdateServer(req *types.UpdateServerRequest) error { + data, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.Id) + if err != nil { + l.Errorf("[UpdateServer] FindOneServer Error: %v", err.Error()) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find server error: %v", err.Error()) + } + data.Name = req.Name + data.Country = req.Country + data.City = req.City + // only update address when it's different + if req.Address != data.Address { + // query server ip location + result, err := ip.GetRegionByIp(req.Address) + if err != nil { + l.Errorf("[UpdateServer] GetRegionByIp Error: %v", err.Error()) + } else { + data.City = result.City + data.Country = result.Country + } + // update address + data.Address = req.Address + } + protocols := make([]node.Protocol, 0) + for _, item := range req.Protocols { + if item.Type == "" { + return errors.Wrapf(xerr.NewErrCodeMsg(xerr.InvalidParams, "protocols type is empty"), "protocols type is empty") + } + var protocol node.Protocol + tool.DeepCopy(&protocol, item) + + // VLESS Reality Key Generation + if protocol.Type == "vless" { + if protocol.Security == "reality" { + if protocol.RealityPublicKey == "" { + public, private, err := tool.Curve25519Genkey(false, "") + if err != nil { + l.Errorf("[CreateServer] Generate Reality Key Error: %v", err.Error()) + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "generate reality key error: %v", err) + } + protocol.RealityPublicKey = public + protocol.RealityPrivateKey = private + protocol.RealityShortId = tool.GenerateShortID(private) + } + if protocol.RealityServerAddr == "" { + protocol.RealityServerAddr = protocol.SNI + } + if protocol.RealityServerPort == 0 { + protocol.RealityServerPort = 443 + } + } + + } + // ShadowSocks 2022 Key Generation + if protocol.Type == "shadowsocks" { + if strings.Contains(protocol.Cipher, "2022") { + var length int + switch protocol.Cipher { + case "2022-blake3-aes-128-gcm": + length = 16 + default: + length = 32 + } + if len(protocol.ServerKey) != length { + protocol.ServerKey = tool.GenerateCipher(protocol.ServerKey, length) + } + } + } + protocols = append(protocols, protocol) + } + err = data.MarshalProtocols(protocols) + if err != nil { + l.Errorf("[UpdateServer] Marshal Protocols Error: %v", err.Error()) + return errors.Wrapf(xerr.NewErrCodeMsg(xerr.InvalidParams, "protocols marshal error"), "protocols marshal error: %v", err) + } + + err = l.svcCtx.NodeModel.UpdateServer(l.ctx, data) + if err != nil { + l.Errorf("[UpdateServer] UpdateServer Error: %v", err.Error()) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update server error: %v", err.Error()) + } + + return l.svcCtx.NodeModel.ClearNodeCache(l.ctx, &node.FilterNodeParams{ + Page: 1, + Size: 1000, + ServerId: []int64{req.Id}, + Search: "", + }) +} diff --git a/internal/logic/admin/subscribe/batchDeleteSubscribeGroupLogic.go b/internal/logic/admin/subscribe/batchDeleteSubscribeGroupLogic.go index 7bc9189..0dbe21c 100644 --- a/internal/logic/admin/subscribe/batchDeleteSubscribeGroupLogic.go +++ b/internal/logic/admin/subscribe/batchDeleteSubscribeGroupLogic.go @@ -3,11 +3,11 @@ package subscribe import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/subscribe" + "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" ) diff --git a/internal/logic/admin/subscribe/batchDeleteSubscribeLogic.go b/internal/logic/admin/subscribe/batchDeleteSubscribeLogic.go index eaee569..c5ddba4 100644 --- a/internal/logic/admin/subscribe/batchDeleteSubscribeLogic.go +++ b/internal/logic/admin/subscribe/batchDeleteSubscribeLogic.go @@ -3,14 +3,14 @@ package subscribe import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type BatchDeleteSubscribeLogic struct { diff --git a/internal/logic/admin/subscribe/createSubscribeGroupLogic.go b/internal/logic/admin/subscribe/createSubscribeGroupLogic.go index e21d2ea..dd6d7a9 100644 --- a/internal/logic/admin/subscribe/createSubscribeGroupLogic.go +++ b/internal/logic/admin/subscribe/createSubscribeGroupLogic.go @@ -3,11 +3,11 @@ package subscribe import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/subscribe" + "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" ) diff --git a/internal/logic/admin/subscribe/createSubscribeLogic.go b/internal/logic/admin/subscribe/createSubscribeLogic.go index 1c8c912..bf50d6a 100644 --- a/internal/logic/admin/subscribe/createSubscribeLogic.go +++ b/internal/logic/admin/subscribe/createSubscribeLogic.go @@ -4,12 +4,12 @@ import ( "context" "encoding/json" - "github.com/perfect-panel/ppanel-server/internal/model/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/subscribe" + "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" ) @@ -37,6 +37,7 @@ func (l *CreateSubscribeLogic) CreateSubscribe(req *types.CreateSubscribeRequest sub := &subscribe.Subscribe{ Id: 0, Name: req.Name, + Language: req.Language, Description: req.Description, UnitPrice: req.UnitPrice, UnitTime: req.UnitTime, @@ -47,9 +48,8 @@ func (l *CreateSubscribeLogic) CreateSubscribe(req *types.CreateSubscribeRequest SpeedLimit: req.SpeedLimit, DeviceLimit: req.DeviceLimit, Quota: req.Quota, - GroupId: req.GroupId, - ServerGroup: tool.Int64SliceToString(req.ServerGroup), - Server: tool.Int64SliceToString(req.Server), + Nodes: tool.Int64SliceToString(req.Nodes), + NodeTags: tool.StringSliceToString(req.NodeTags), Show: req.Show, Sell: req.Sell, Sort: 0, diff --git a/internal/logic/admin/subscribe/deleteSubscribeGroupLogic.go b/internal/logic/admin/subscribe/deleteSubscribeGroupLogic.go index 655febc..72c3a08 100644 --- a/internal/logic/admin/subscribe/deleteSubscribeGroupLogic.go +++ b/internal/logic/admin/subscribe/deleteSubscribeGroupLogic.go @@ -3,11 +3,11 @@ package subscribe import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/subscribe" + "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" ) diff --git a/internal/logic/admin/subscribe/deleteSubscribeLogic.go b/internal/logic/admin/subscribe/deleteSubscribeLogic.go index 44d22fc..6ba4434 100644 --- a/internal/logic/admin/subscribe/deleteSubscribeLogic.go +++ b/internal/logic/admin/subscribe/deleteSubscribeLogic.go @@ -3,13 +3,13 @@ package subscribe import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/user" + "github.com/perfect-panel/server/internal/model/user" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/subscribe/getSubscribeDetailsLogic.go b/internal/logic/admin/subscribe/getSubscribeDetailsLogic.go index 26f4be4..6defdf1 100644 --- a/internal/logic/admin/subscribe/getSubscribeDetailsLogic.go +++ b/internal/logic/admin/subscribe/getSubscribeDetailsLogic.go @@ -3,12 +3,13 @@ package subscribe import ( "context" "encoding/json" + "strings" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) @@ -41,7 +42,7 @@ func (l *GetSubscribeDetailsLogic) GetSubscribeDetails(req *types.GetSubscribeDe l.Logger.Error("[GetSubscribeDetailsLogic] JSON unmarshal failed: ", logger.Field("error", err.Error()), logger.Field("discount", sub.Discount)) } } - resp.Server = tool.StringToInt64Slice(sub.Server) - resp.ServerGroup = tool.StringToInt64Slice(sub.ServerGroup) + resp.Nodes = tool.StringToInt64Slice(sub.Nodes) + resp.NodeTags = strings.Split(sub.NodeTags, ",") return resp, nil } diff --git a/internal/logic/admin/subscribe/getSubscribeGroupListLogic.go b/internal/logic/admin/subscribe/getSubscribeGroupListLogic.go index 81c65b7..7df5b55 100644 --- a/internal/logic/admin/subscribe/getSubscribeGroupListLogic.go +++ b/internal/logic/admin/subscribe/getSubscribeGroupListLogic.go @@ -3,12 +3,12 @@ package subscribe import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/subscribe" + "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" ) diff --git a/internal/logic/admin/subscribe/getSubscribeListLogic.go b/internal/logic/admin/subscribe/getSubscribeListLogic.go index 20d5455..e8c7866 100644 --- a/internal/logic/admin/subscribe/getSubscribeListLogic.go +++ b/internal/logic/admin/subscribe/getSubscribeListLogic.go @@ -3,12 +3,14 @@ package subscribe import ( "context" "encoding/json" + "strings" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/subscribe" + "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" ) @@ -28,7 +30,12 @@ func NewGetSubscribeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) * } func (l *GetSubscribeListLogic) GetSubscribeList(req *types.GetSubscribeListRequest) (resp *types.GetSubscribeListResponse, err error) { - total, list, err := l.svcCtx.SubscribeModel.QuerySubscribeListByPage(l.ctx, int(req.Page), int(req.Size), req.GroupId, req.Search) + total, list, err := l.svcCtx.SubscribeModel.FilterList(l.ctx, &subscribe.FilterParams{ + Page: int(req.Page), + Size: int(req.Size), + Language: req.Language, + Search: req.Search, + }) if err != nil { l.Logger.Error("[GetSubscribeListLogic] get subscribe list failed: ", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get subscribe list failed: %v", err.Error()) @@ -47,8 +54,8 @@ func (l *GetSubscribeListLogic) GetSubscribeList(req *types.GetSubscribeListRequ l.Logger.Error("[GetSubscribeListLogic] JSON unmarshal failed: ", logger.Field("error", err.Error()), logger.Field("discount", item.Discount)) } } - sub.Server = tool.StringToInt64Slice(item.Server) - sub.ServerGroup = tool.StringToInt64Slice(item.ServerGroup) + sub.Nodes = tool.StringToInt64Slice(item.Nodes) + sub.NodeTags = strings.Split(item.NodeTags, ",") resultList = append(resultList, sub) } @@ -59,8 +66,8 @@ func (l *GetSubscribeListLogic) GetSubscribeList(req *types.GetSubscribeListRequ } for i, item := range resultList { - if subscribe, ok := subscribeMaps[item.Id]; ok { - resultList[i].Sold = subscribe + if sub, ok := subscribeMaps[item.Id]; ok { + resultList[i].Sold = sub } } diff --git a/internal/logic/admin/subscribe/subscribeSortLogic.go b/internal/logic/admin/subscribe/subscribeSortLogic.go index f4d7b08..9a5cb5b 100644 --- a/internal/logic/admin/subscribe/subscribeSortLogic.go +++ b/internal/logic/admin/subscribe/subscribeSortLogic.go @@ -3,13 +3,14 @@ package subscribe import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/subscribe" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type SubscribeSortLogic struct { @@ -40,7 +41,11 @@ func (l *SubscribeSortLogic) SubscribeSort(req *types.SubscribeSortRequest) erro l.Logger.Error("[SubscribeSortLogic] query subscribe list by ids error: ", logger.Field("error", err.Error())) return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query subscribe list by ids error: %v", err.Error()) } - subs, err := l.svcCtx.SubscribeModel.QuerySubscribeListByIds(l.ctx, ids) + _, subs, err := l.svcCtx.SubscribeModel.FilterList(l.ctx, &subscribe.FilterParams{ + Page: 1, + Size: 9999, + Ids: ids, + }) if err != nil { l.Logger.Error("[SubscribeSortLogic] query subscribe list by ids error: ", logger.Field("error", err.Error())) return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query subscribe list by ids error: %v", err.Error()) diff --git a/internal/logic/admin/subscribe/updateSubscribeGroupLogic.go b/internal/logic/admin/subscribe/updateSubscribeGroupLogic.go index 5eb4119..2f8871b 100644 --- a/internal/logic/admin/subscribe/updateSubscribeGroupLogic.go +++ b/internal/logic/admin/subscribe/updateSubscribeGroupLogic.go @@ -3,11 +3,11 @@ package subscribe import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/subscribe" + "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" ) diff --git a/internal/logic/admin/subscribe/updateSubscribeLogic.go b/internal/logic/admin/subscribe/updateSubscribeLogic.go index e61df40..060af5a 100644 --- a/internal/logic/admin/subscribe/updateSubscribeLogic.go +++ b/internal/logic/admin/subscribe/updateSubscribeLogic.go @@ -4,14 +4,14 @@ import ( "context" "encoding/json" - "github.com/perfect-panel/ppanel-server/pkg/device" + "github.com/perfect-panel/server/pkg/device" - "github.com/perfect-panel/ppanel-server/internal/model/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/subscribe" + "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" ) @@ -45,6 +45,7 @@ func (l *UpdateSubscribeLogic) UpdateSubscribe(req *types.UpdateSubscribeRequest sub := &subscribe.Subscribe{ Id: req.Id, Name: req.Name, + Language: req.Language, Description: req.Description, UnitPrice: req.UnitPrice, UnitTime: req.UnitTime, @@ -55,9 +56,8 @@ func (l *UpdateSubscribeLogic) UpdateSubscribe(req *types.UpdateSubscribeRequest SpeedLimit: req.SpeedLimit, DeviceLimit: req.DeviceLimit, Quota: req.Quota, - GroupId: req.GroupId, - ServerGroup: tool.Int64SliceToString(req.ServerGroup), - Server: tool.Int64SliceToString(req.Server), + Nodes: tool.Int64SliceToString(req.Nodes), + NodeTags: tool.StringSliceToString(req.NodeTags), Show: req.Show, Sell: req.Sell, Sort: req.Sort, diff --git a/internal/logic/admin/system/createApplicationLogic.go b/internal/logic/admin/system/createApplicationLogic.go deleted file mode 100644 index 89aeecb..0000000 --- a/internal/logic/admin/system/createApplicationLogic.go +++ /dev/null @@ -1,125 +0,0 @@ -package system - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/model/application" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type CreateApplicationLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewCreateApplicationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateApplicationLogic { - return &CreateApplicationLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *CreateApplicationLogic) CreateApplication(req *types.CreateApplicationRequest) error { - var ios []application.ApplicationVersion - if len(req.Platform.IOS) > 0 { - for _, ios_ := range req.Platform.IOS { - ios = append(ios, application.ApplicationVersion{ - Url: ios_.Url, - Version: ios_.Version, - Platform: "ios", - IsDefault: ios_.IsDefault, - Description: ios_.Description, - }) - } - } - - var mac []application.ApplicationVersion - if len(req.Platform.MacOS) > 0 { - for _, mac_ := range req.Platform.MacOS { - mac = append(mac, application.ApplicationVersion{ - Url: mac_.Url, - Version: mac_.Version, - Platform: "macos", - IsDefault: mac_.IsDefault, - Description: mac_.Description, - }) - } - } - - var linux []application.ApplicationVersion - if len(req.Platform.Linux) > 0 { - for _, linux_ := range req.Platform.Linux { - linux = append(linux, application.ApplicationVersion{ - Url: linux_.Url, - Version: linux_.Version, - Platform: "linux", - IsDefault: linux_.IsDefault, - Description: linux_.Description, - }) - } - } - - var android []application.ApplicationVersion - if len(req.Platform.Android) > 0 { - for _, android_ := range req.Platform.Android { - android = append(android, application.ApplicationVersion{ - Url: android_.Url, - Version: android_.Version, - Platform: "android", - IsDefault: android_.IsDefault, - Description: android_.Description, - }) - } - } - - var windows []application.ApplicationVersion - if len(req.Platform.Windows) > 0 { - for _, windows_ := range req.Platform.Windows { - windows = append(windows, application.ApplicationVersion{ - Url: windows_.Url, - Version: windows_.Version, - Platform: "windows", - IsDefault: windows_.IsDefault, - Description: windows_.Description, - }) - } - } - - var harmony []application.ApplicationVersion - if len(req.Platform.Harmony) > 0 { - for _, harmony_ := range req.Platform.Harmony { - harmony = append(harmony, application.ApplicationVersion{ - Url: harmony_.Url, - Version: harmony_.Version, - Platform: "harmony", - IsDefault: harmony_.IsDefault, - Description: harmony_.Description, - }) - } - } - var applicationVersions []application.ApplicationVersion - applicationVersions = append(applicationVersions, ios...) - applicationVersions = append(applicationVersions, mac...) - applicationVersions = append(applicationVersions, linux...) - applicationVersions = append(applicationVersions, android...) - applicationVersions = append(applicationVersions, windows...) - applicationVersions = append(applicationVersions, harmony...) - app := application.Application{ - Name: req.Name, - Icon: req.Icon, - SubscribeType: req.SubscribeType, - ApplicationVersions: applicationVersions, - } - err := l.svcCtx.ApplicationModel.Insert(l.ctx, &app) - if err != nil { - l.Errorw("[CreateApplicationLogic] create application error: ", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create application error: %v", err) - } - return nil -} diff --git a/internal/logic/admin/system/createApplicationVersionLogic.go b/internal/logic/admin/system/createApplicationVersionLogic.go deleted file mode 100644 index 7ee2033..0000000 --- a/internal/logic/admin/system/createApplicationVersionLogic.go +++ /dev/null @@ -1,44 +0,0 @@ -package system - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/model/application" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type CreateApplicationVersionLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Create application version -func NewCreateApplicationVersionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateApplicationVersionLogic { - return &CreateApplicationVersionLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *CreateApplicationVersionLogic) CreateApplicationVersion(req *types.CreateApplicationVersionRequest) error { - create := &application.ApplicationVersion{ - Url: req.Url, - Platform: req.Platform, - Version: req.Version, - Description: req.Description, - IsDefault: req.IsDefault, - ApplicationId: req.ApplicationId, - } - err := l.svcCtx.ApplicationModel.InsertVersion(l.ctx, create) - if err != nil { - l.Errorw("[CreateApplicationVersion] create application version error: ", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create application version error: %v", err) - } - return nil -} diff --git a/internal/logic/admin/system/deleteApplicationLogic.go b/internal/logic/admin/system/deleteApplicationLogic.go deleted file mode 100644 index 4e7e489..0000000 --- a/internal/logic/admin/system/deleteApplicationLogic.go +++ /dev/null @@ -1,35 +0,0 @@ -package system - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type DeleteApplicationLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewDeleteApplicationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteApplicationLogic { - return &DeleteApplicationLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *DeleteApplicationLogic) DeleteApplication(req *types.DeleteApplicationRequest) error { - // delete application - err := l.svcCtx.ApplicationModel.Delete(l.ctx, req.Id) - if err != nil { - l.Errorw("[DeleteApplicationLogic] delete application error: ", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete application error: %v", err.Error()) - } - return nil -} diff --git a/internal/logic/admin/system/deleteApplicationVersionLogic.go b/internal/logic/admin/system/deleteApplicationVersionLogic.go deleted file mode 100644 index f2235e5..0000000 --- a/internal/logic/admin/system/deleteApplicationVersionLogic.go +++ /dev/null @@ -1,36 +0,0 @@ -package system - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type DeleteApplicationVersionLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Delete application -func NewDeleteApplicationVersionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteApplicationVersionLogic { - return &DeleteApplicationVersionLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *DeleteApplicationVersionLogic) DeleteApplicationVersion(req *types.DeleteApplicationVersionRequest) error { - // delete application - err := l.svcCtx.ApplicationModel.DeleteVersion(l.ctx, req.Id) - if err != nil { - l.Errorw("[DeleteApplicationVersion] delete application version error: ", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete application version error: %v", err.Error()) - } - return nil -} diff --git a/internal/logic/admin/system/getApplicationConfigLogic.go b/internal/logic/admin/system/getApplicationConfigLogic.go deleted file mode 100644 index 39a853f..0000000 --- a/internal/logic/admin/system/getApplicationConfigLogic.go +++ /dev/null @@ -1,49 +0,0 @@ -package system - -import ( - "context" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -type GetApplicationConfigLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// get application config -func NewGetApplicationConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetApplicationConfigLogic { - return &GetApplicationConfigLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *GetApplicationConfigLogic) GetApplicationConfig() (resp *types.ApplicationConfig, err error) { - resp = &types.ApplicationConfig{} - appConfig, err := l.svcCtx.ApplicationModel.FindOneConfig(l.ctx, 1) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - err = nil - return - } - l.Errorw("[GetApplicationConfig] Database Error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get app config error: %v", err.Error()) - } - resp.AppId = appConfig.AppId - resp.EncryptionKey = appConfig.EncryptionKey - resp.EncryptionMethod = appConfig.EncryptionMethod - resp.Domains = strings.Split(appConfig.Domains, ";") - resp.StartupPicture = appConfig.StartupPicture - resp.StartupPictureSkipTime = appConfig.StartupPictureSkipTime - return -} diff --git a/internal/logic/admin/system/getApplicationLogic.go b/internal/logic/admin/system/getApplicationLogic.go deleted file mode 100644 index 7c94d5c..0000000 --- a/internal/logic/admin/system/getApplicationLogic.go +++ /dev/null @@ -1,113 +0,0 @@ -package system - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/model/application" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -type GetApplicationLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get application -func NewGetApplicationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetApplicationLogic { - return &GetApplicationLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *GetApplicationLogic) GetApplication() (resp *types.ApplicationResponse, err error) { - resp = &types.ApplicationResponse{} - var applications []*application.Application - err = l.svcCtx.ApplicationModel.Transaction(l.ctx, func(tx *gorm.DB) (err error) { - return tx.Model(applications).Preload("ApplicationVersions").Find(&applications).Error - }) - if err != nil { - l.Errorw("[GetApplicationLogic] get application error: ", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get application error: %v", err.Error()) - } - - if len(applications) == 0 { - return resp, nil - } - - for _, app := range applications { - applicationResponse := types.ApplicationResponseInfo{ - Id: app.Id, - Name: app.Name, - Icon: app.Icon, - Description: app.Description, - SubscribeType: app.SubscribeType, - } - applicationVersions := app.ApplicationVersions - if len(applicationVersions) != 0 { - for _, applicationVersion := range applicationVersions { - switch applicationVersion.Platform { - case "ios": - applicationResponse.Platform.IOS = append(applicationResponse.Platform.IOS, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "macos": - applicationResponse.Platform.MacOS = append(applicationResponse.Platform.MacOS, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "linux": - applicationResponse.Platform.Linux = append(applicationResponse.Platform.Linux, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "android": - applicationResponse.Platform.Android = append(applicationResponse.Platform.Android, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "windows": - applicationResponse.Platform.Windows = append(applicationResponse.Platform.Windows, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "harmony": - applicationResponse.Platform.Harmony = append(applicationResponse.Platform.Harmony, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - } - } - } - resp.Applications = append(resp.Applications, applicationResponse) - } - - return -} diff --git a/internal/logic/admin/system/getCurrencyConfigLogic.go b/internal/logic/admin/system/getCurrencyConfigLogic.go index 42286b3..32239a0 100644 --- a/internal/logic/admin/system/getCurrencyConfigLogic.go +++ b/internal/logic/admin/system/getCurrencyConfigLogic.go @@ -3,11 +3,11 @@ package system import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/system/getInviteConfigLogic.go b/internal/logic/admin/system/getInviteConfigLogic.go index b12ac8f..e5786e8 100644 --- a/internal/logic/admin/system/getInviteConfigLogic.go +++ b/internal/logic/admin/system/getInviteConfigLogic.go @@ -3,11 +3,11 @@ package system import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/system/getNodeConfigLogic.go b/internal/logic/admin/system/getNodeConfigLogic.go index caa647a..1212b1a 100644 --- a/internal/logic/admin/system/getNodeConfigLogic.go +++ b/internal/logic/admin/system/getNodeConfigLogic.go @@ -2,12 +2,13 @@ package system import ( "context" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "encoding/json" + "github.com/perfect-panel/server/internal/config" + "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" ) @@ -26,15 +27,45 @@ func NewGetNodeConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Get } func (l *GetNodeConfigLogic) GetNodeConfig() (*types.NodeConfig, error) { - resp := &types.NodeConfig{} - // get server config from db configs, err := l.svcCtx.SystemModel.GetNodeConfig(l.ctx) if err != nil { l.Errorw("[GetNodeConfigLogic] GetNodeConfig get server config error: ", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetNodeConfig get server config error: %v", err.Error()) } - // reflect to response - tool.SystemConfigSliceReflectToStruct(configs, resp) - return resp, nil + var dbConfig config.NodeDBConfig + tool.SystemConfigSliceReflectToStruct(configs, &dbConfig) + c := &types.NodeConfig{ + NodeSecret: dbConfig.NodeSecret, + NodePullInterval: dbConfig.NodePullInterval, + NodePushInterval: dbConfig.NodePushInterval, + IPStrategy: dbConfig.IPStrategy, + TrafficReportThreshold: dbConfig.TrafficReportThreshold, + } + + if dbConfig.DNS != "" { + var dns []types.NodeDNS + err = json.Unmarshal([]byte(dbConfig.DNS), &dns) + if err != nil { + logger.Errorf("[Node] Unmarshal DNS config error: %s", err.Error()) + panic(err) + } + c.DNS = dns + } + if dbConfig.Block != "" { + var block []string + _ = json.Unmarshal([]byte(dbConfig.Block), &block) + c.Block = tool.RemoveDuplicateElements(block...) + } + if dbConfig.Outbound != "" { + var outbound []types.NodeOutbound + err = json.Unmarshal([]byte(dbConfig.Outbound), &outbound) + if err != nil { + logger.Errorf("[Node] Unmarshal Outbound config error: %s", err.Error()) + panic(err) + } + c.Outbound = outbound + } + + return c, nil } diff --git a/internal/logic/admin/system/getNodeMultiplierLogic.go b/internal/logic/admin/system/getNodeMultiplierLogic.go index cc330e4..e580202 100644 --- a/internal/logic/admin/system/getNodeMultiplierLogic.go +++ b/internal/logic/admin/system/getNodeMultiplierLogic.go @@ -4,10 +4,10 @@ import ( "context" "encoding/json" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/system/getPrivacyPolicyConfigLogic.go b/internal/logic/admin/system/getPrivacyPolicyConfigLogic.go index 8c8d15d..b0c6844 100644 --- a/internal/logic/admin/system/getPrivacyPolicyConfigLogic.go +++ b/internal/logic/admin/system/getPrivacyPolicyConfigLogic.go @@ -3,11 +3,11 @@ package system import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/system/getRegisterConfigLogic.go b/internal/logic/admin/system/getRegisterConfigLogic.go index 7fcdf3a..7ba002f 100644 --- a/internal/logic/admin/system/getRegisterConfigLogic.go +++ b/internal/logic/admin/system/getRegisterConfigLogic.go @@ -3,11 +3,11 @@ package system import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/system/getSiteConfigLogic.go b/internal/logic/admin/system/getSiteConfigLogic.go index ec7890d..7787ecc 100644 --- a/internal/logic/admin/system/getSiteConfigLogic.go +++ b/internal/logic/admin/system/getSiteConfigLogic.go @@ -3,11 +3,11 @@ package system import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/system/getSubscribeConfigLogic.go b/internal/logic/admin/system/getSubscribeConfigLogic.go index 4e36c81..71ecd6f 100644 --- a/internal/logic/admin/system/getSubscribeConfigLogic.go +++ b/internal/logic/admin/system/getSubscribeConfigLogic.go @@ -3,11 +3,11 @@ package system import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/system/getSubscribeTypeLogic.go b/internal/logic/admin/system/getSubscribeTypeLogic.go deleted file mode 100644 index 41ab1f9..0000000 --- a/internal/logic/admin/system/getSubscribeTypeLogic.go +++ /dev/null @@ -1,42 +0,0 @@ -package system - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/model/subscribeType" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type GetSubscribeTypeLogic struct { - ctx context.Context - svcCtx *svc.ServiceContext - logger.Logger -} - -func NewGetSubscribeTypeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetSubscribeTypeLogic { - return &GetSubscribeTypeLogic{ - ctx: ctx, - svcCtx: svcCtx, - Logger: logger.WithContext(ctx), - } -} - -func (l *GetSubscribeTypeLogic) GetSubscribeType() (resp *types.SubscribeType, err error) { - var list []*subscribeType.SubscribeType - err = l.svcCtx.DB.Model(&subscribeType.SubscribeType{}).Find(&list).Error - if err != nil { - l.Errorw("[GetSubscribeType] get subscribe type failed", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get subscribe type failed: %v", err) - } - typeList := make([]string, 0) - for _, item := range list { - typeList = append(typeList, item.Name) - } - return &types.SubscribeType{ - SubscribeTypes: typeList, - }, nil -} diff --git a/internal/logic/admin/system/getTosConfigLogic.go b/internal/logic/admin/system/getTosConfigLogic.go index be2e9db..80a70fb 100644 --- a/internal/logic/admin/system/getTosConfigLogic.go +++ b/internal/logic/admin/system/getTosConfigLogic.go @@ -3,11 +3,11 @@ package system import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/system/getVerifyCodeConfigLogic.go b/internal/logic/admin/system/getVerifyCodeConfigLogic.go index 52db722..13c931d 100644 --- a/internal/logic/admin/system/getVerifyCodeConfigLogic.go +++ b/internal/logic/admin/system/getVerifyCodeConfigLogic.go @@ -3,11 +3,11 @@ package system import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/system/getVerifyConfigLogic.go b/internal/logic/admin/system/getVerifyConfigLogic.go index 5568430..044ba22 100644 --- a/internal/logic/admin/system/getVerifyConfigLogic.go +++ b/internal/logic/admin/system/getVerifyConfigLogic.go @@ -3,12 +3,12 @@ package system import ( "context" - "github.com/perfect-panel/ppanel-server/initialize" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/initialize" + "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" ) diff --git a/internal/logic/admin/system/preViewNodeMultiplierLogic.go b/internal/logic/admin/system/preViewNodeMultiplierLogic.go new file mode 100644 index 0000000..58d1dd5 --- /dev/null +++ b/internal/logic/admin/system/preViewNodeMultiplierLogic.go @@ -0,0 +1,33 @@ +package system + +import ( + "context" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" + "time" +) + +type PreViewNodeMultiplierLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// PreView Node Multiplier +func NewPreViewNodeMultiplierLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PreViewNodeMultiplierLogic { + return &PreViewNodeMultiplierLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *PreViewNodeMultiplierLogic) PreViewNodeMultiplier() (resp *types.PreViewNodeMultiplierResponse, err error) { + now := time.Now() + ratio := l.svcCtx.NodeMultiplierManager.GetMultiplier(now) + return &types.PreViewNodeMultiplierResponse{ + Ratio: ratio, + CurrentTime: now.Format("2006-01-02 15:04:05"), + }, nil +} diff --git a/internal/logic/admin/system/setNodeMultiplierLogic.go b/internal/logic/admin/system/setNodeMultiplierLogic.go index 4f4b6ae..78edf50 100644 --- a/internal/logic/admin/system/setNodeMultiplierLogic.go +++ b/internal/logic/admin/system/setNodeMultiplierLogic.go @@ -4,10 +4,10 @@ import ( "context" "encoding/json" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/system/settingTelegramBotLogic.go b/internal/logic/admin/system/settingTelegramBotLogic.go index c919743..2986860 100644 --- a/internal/logic/admin/system/settingTelegramBotLogic.go +++ b/internal/logic/admin/system/settingTelegramBotLogic.go @@ -3,10 +3,10 @@ package system import ( "context" - "github.com/perfect-panel/ppanel-server/initialize" + "github.com/perfect-panel/server/initialize" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/logger" ) type SettingTelegramBotLogic struct { diff --git a/internal/logic/admin/system/updateApplicationConfigLogic.go b/internal/logic/admin/system/updateApplicationConfigLogic.go deleted file mode 100644 index 2b5c936..0000000 --- a/internal/logic/admin/system/updateApplicationConfigLogic.go +++ /dev/null @@ -1,45 +0,0 @@ -package system - -import ( - "context" - "strings" - - "github.com/perfect-panel/ppanel-server/internal/model/application" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type UpdateApplicationConfigLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// update application config -func NewUpdateApplicationConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateApplicationConfigLogic { - return &UpdateApplicationConfigLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *UpdateApplicationConfigLogic) UpdateApplicationConfig(req *types.ApplicationConfig) error { - err := l.svcCtx.ApplicationModel.UpdateConfig(l.ctx, &application.ApplicationConfig{ - Id: 1, - AppId: req.AppId, - EncryptionKey: req.EncryptionKey, - EncryptionMethod: req.EncryptionMethod, - Domains: strings.Join(req.Domains, ";"), - StartupPicture: req.StartupPicture, - StartupPictureSkipTime: req.StartupPictureSkipTime, - }) - if err != nil { - l.Errorw("[UpdateApplicationConfig] Database Error", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update app config error: %v", err.Error()) - } - return nil -} diff --git a/internal/logic/admin/system/updateApplicationLogic.go b/internal/logic/admin/system/updateApplicationLogic.go deleted file mode 100644 index dd12e22..0000000 --- a/internal/logic/admin/system/updateApplicationLogic.go +++ /dev/null @@ -1,149 +0,0 @@ -package system - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/model/application" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" -) - -type UpdateApplicationLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewUpdateApplicationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateApplicationLogic { - return &UpdateApplicationLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *UpdateApplicationLogic) UpdateApplication(req *types.UpdateApplicationRequest) error { - - // find application - app, err := l.svcCtx.ApplicationModel.FindOne(l.ctx, req.Id) - if err != nil { - l.Errorw("[UpdateApplication] find application error", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find application error: %v", err.Error()) - } - app.Name = req.Name - app.Icon = req.Icon - app.SubscribeType = req.SubscribeType - app.Description = req.Description - - var ios []application.ApplicationVersion - if len(req.Platform.IOS) > 0 { - for _, ios_ := range req.Platform.IOS { - ios = append(ios, application.ApplicationVersion{ - Url: ios_.Url, - Version: ios_.Version, - Platform: "ios", - IsDefault: ios_.IsDefault, - Description: ios_.Description, - ApplicationId: app.Id, - }) - } - } - - var mac []application.ApplicationVersion - if len(req.Platform.MacOS) > 0 { - for _, mac_ := range req.Platform.MacOS { - mac = append(mac, application.ApplicationVersion{ - Url: mac_.Url, - Version: mac_.Version, - Platform: "macos", - IsDefault: mac_.IsDefault, - Description: mac_.Description, - ApplicationId: app.Id, - }) - } - } - - var linux []application.ApplicationVersion - if len(req.Platform.Linux) > 0 { - for _, linux_ := range req.Platform.Linux { - linux = append(linux, application.ApplicationVersion{ - Url: linux_.Url, - Version: linux_.Version, - Platform: "linux", - IsDefault: linux_.IsDefault, - Description: linux_.Description, - ApplicationId: app.Id, - }) - } - } - - var android []application.ApplicationVersion - if len(req.Platform.Android) > 0 { - for _, android_ := range req.Platform.Android { - android = append(android, application.ApplicationVersion{ - Url: android_.Url, - Version: android_.Version, - Platform: "android", - IsDefault: android_.IsDefault, - Description: android_.Description, - ApplicationId: app.Id, - }) - } - } - - var windows []application.ApplicationVersion - if len(req.Platform.Windows) > 0 { - for _, windows_ := range req.Platform.Windows { - windows = append(windows, application.ApplicationVersion{ - Url: windows_.Url, - Version: windows_.Version, - Platform: "windows", - IsDefault: windows_.IsDefault, - Description: windows_.Description, - ApplicationId: app.Id, - }) - } - } - - var harmony []application.ApplicationVersion - if len(req.Platform.Harmony) > 0 { - for _, harmony_ := range req.Platform.Harmony { - harmony = append(harmony, application.ApplicationVersion{ - Url: harmony_.Url, - Version: harmony_.Version, - Platform: "harmony", - IsDefault: harmony_.IsDefault, - Description: harmony_.Description, - ApplicationId: app.Id, - }) - } - } - var applicationVersions []application.ApplicationVersion - applicationVersions = append(applicationVersions, ios...) - applicationVersions = append(applicationVersions, mac...) - applicationVersions = append(applicationVersions, linux...) - applicationVersions = append(applicationVersions, android...) - applicationVersions = append(applicationVersions, windows...) - applicationVersions = append(applicationVersions, harmony...) - app.ApplicationVersions = applicationVersions - err = l.svcCtx.ApplicationModel.Transaction(l.ctx, func(db *gorm.DB) error { - - if err = db.Where("application_id = ?", app.Id).Delete(&application.ApplicationVersion{}).Error; err != nil { - return err - } - if err = db.Create(&applicationVersions).Error; err != nil { - return err - } - return db.Save(app).Error - }) - if err != nil { - l.Errorw("[UpdateApplication] update application error", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update application error: %v", err.Error()) - } - return nil -} diff --git a/internal/logic/admin/system/updateApplicationVersionLogic.go b/internal/logic/admin/system/updateApplicationVersionLogic.go deleted file mode 100644 index ce33db0..0000000 --- a/internal/logic/admin/system/updateApplicationVersionLogic.go +++ /dev/null @@ -1,45 +0,0 @@ -package system - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type UpdateApplicationVersionLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Update application version -func NewUpdateApplicationVersionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateApplicationVersionLogic { - return &UpdateApplicationVersionLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *UpdateApplicationVersionLogic) UpdateApplicationVersion(req *types.UpdateApplicationVersionRequest) error { - // find application - app, err := l.svcCtx.ApplicationModel.FindOneVersion(l.ctx, req.Id) - if err != nil { - l.Errorw("[UpdateApplicationVersion] find application version error", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find application error: %v", err.Error()) - } - app.Url = req.Url - app.Version = req.Version - app.Description = req.Description - app.IsDefault = req.IsDefault - err = l.svcCtx.ApplicationModel.UpdateVersion(l.ctx, app) - if err != nil { - l.Errorw("[UpdateApplicationVersion] update application version error", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update application version error: %v", err.Error()) - } - return nil -} diff --git a/internal/logic/admin/system/updateCurrencyConfigLogic.go b/internal/logic/admin/system/updateCurrencyConfigLogic.go index 8bd2326..0104331 100644 --- a/internal/logic/admin/system/updateCurrencyConfigLogic.go +++ b/internal/logic/admin/system/updateCurrencyConfigLogic.go @@ -4,16 +4,16 @@ import ( "context" "reflect" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/system" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type UpdateCurrencyConfigLogic struct { diff --git a/internal/logic/admin/system/updateInviteConfigLogic.go b/internal/logic/admin/system/updateInviteConfigLogic.go index 8ebc555..8e2201f 100644 --- a/internal/logic/admin/system/updateInviteConfigLogic.go +++ b/internal/logic/admin/system/updateInviteConfigLogic.go @@ -4,18 +4,18 @@ import ( "context" "reflect" - "github.com/perfect-panel/ppanel-server/initialize" + "github.com/perfect-panel/server/initialize" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/system" + "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" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" ) type UpdateInviteConfigLogic struct { diff --git a/internal/logic/admin/system/updateNodeConfigLogic.go b/internal/logic/admin/system/updateNodeConfigLogic.go index c7523de..dbbba84 100644 --- a/internal/logic/admin/system/updateNodeConfigLogic.go +++ b/internal/logic/admin/system/updateNodeConfigLogic.go @@ -4,17 +4,17 @@ import ( "context" "reflect" - "github.com/perfect-panel/ppanel-server/initialize" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/initialize" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/system" + "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" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" ) type UpdateNodeConfigLogic struct { @@ -41,7 +41,9 @@ func (l *UpdateNodeConfigLogic) UpdateNodeConfig(req *types.NodeConfig) error { // Get the field name fieldName := t.Field(i).Name // Get the field value to string - fieldValue := tool.ConvertValueToString(v.Field(i)) + var fieldValue string + + fieldValue = tool.ConvertValueToString(v.Field(i)) // Update the server config err = db.Model(&system.System{}).Where("`category` = 'server' and `key` = ?", fieldName).Update("value", fieldValue).Error if err != nil { diff --git a/internal/logic/admin/system/updatePrivacyPolicyConfigLogic.go b/internal/logic/admin/system/updatePrivacyPolicyConfigLogic.go index 49f5122..bd8cdf8 100644 --- a/internal/logic/admin/system/updatePrivacyPolicyConfigLogic.go +++ b/internal/logic/admin/system/updatePrivacyPolicyConfigLogic.go @@ -4,16 +4,16 @@ import ( "context" "reflect" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/system" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type UpdatePrivacyPolicyConfigLogic struct { diff --git a/internal/logic/admin/system/updateRegisterConfigLogic.go b/internal/logic/admin/system/updateRegisterConfigLogic.go index b990d4c..d5bf8c3 100644 --- a/internal/logic/admin/system/updateRegisterConfigLogic.go +++ b/internal/logic/admin/system/updateRegisterConfigLogic.go @@ -5,18 +5,18 @@ import ( "reflect" - "github.com/perfect-panel/ppanel-server/initialize" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/initialize" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/system" + "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" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" ) type UpdateRegisterConfigLogic struct { diff --git a/internal/logic/admin/system/updateSiteConfigLogic.go b/internal/logic/admin/system/updateSiteConfigLogic.go index 3e8a3cc..2fc1e0a 100644 --- a/internal/logic/admin/system/updateSiteConfigLogic.go +++ b/internal/logic/admin/system/updateSiteConfigLogic.go @@ -4,13 +4,13 @@ import ( "context" "reflect" - "github.com/perfect-panel/ppanel-server/initialize" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/initialize" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/system" + "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" "gorm.io/gorm" ) diff --git a/internal/logic/admin/system/updateSubscribeConfigLogic.go b/internal/logic/admin/system/updateSubscribeConfigLogic.go index 69b1436..f4e79b4 100644 --- a/internal/logic/admin/system/updateSubscribeConfigLogic.go +++ b/internal/logic/admin/system/updateSubscribeConfigLogic.go @@ -4,14 +4,14 @@ import ( "context" "reflect" - "github.com/perfect-panel/ppanel-server/initialize" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/initialize" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/system" + "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" "gorm.io/gorm" ) diff --git a/internal/logic/admin/system/updateTosConfigLogic.go b/internal/logic/admin/system/updateTosConfigLogic.go index 39d3b5e..94a99f2 100644 --- a/internal/logic/admin/system/updateTosConfigLogic.go +++ b/internal/logic/admin/system/updateTosConfigLogic.go @@ -4,13 +4,13 @@ import ( "context" "reflect" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/system" + "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" "gorm.io/gorm" ) diff --git a/internal/logic/admin/system/updateVerifyCodeConfigLogic.go b/internal/logic/admin/system/updateVerifyCodeConfigLogic.go index 2838b24..e089dd6 100644 --- a/internal/logic/admin/system/updateVerifyCodeConfigLogic.go +++ b/internal/logic/admin/system/updateVerifyCodeConfigLogic.go @@ -4,17 +4,17 @@ import ( "context" "reflect" - "github.com/perfect-panel/ppanel-server/internal/config" + "github.com/perfect-panel/server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/system" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type UpdateVerifyCodeConfigLogic struct { diff --git a/internal/logic/admin/system/updateVerifyConfigLogic.go b/internal/logic/admin/system/updateVerifyConfigLogic.go index 5ba7543..ddb3e55 100644 --- a/internal/logic/admin/system/updateVerifyConfigLogic.go +++ b/internal/logic/admin/system/updateVerifyConfigLogic.go @@ -4,14 +4,14 @@ import ( "context" "reflect" - "github.com/perfect-panel/ppanel-server/initialize" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/initialize" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/system" + "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" "gorm.io/gorm" ) diff --git a/internal/logic/admin/ticket/createTicketFollowLogic.go b/internal/logic/admin/ticket/createTicketFollowLogic.go index 93b3373..c01cd27 100644 --- a/internal/logic/admin/ticket/createTicketFollowLogic.go +++ b/internal/logic/admin/ticket/createTicketFollowLogic.go @@ -3,11 +3,11 @@ package ticket import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/ticket" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/ticket" + "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" ) diff --git a/internal/logic/admin/ticket/getTicketListLogic.go b/internal/logic/admin/ticket/getTicketListLogic.go index 0a16273..c03932a 100644 --- a/internal/logic/admin/ticket/getTicketListLogic.go +++ b/internal/logic/admin/ticket/getTicketListLogic.go @@ -3,11 +3,11 @@ package ticket import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/ticket/getTicketLogic.go b/internal/logic/admin/ticket/getTicketLogic.go index 7aaa826..87a0e2a 100644 --- a/internal/logic/admin/ticket/getTicketLogic.go +++ b/internal/logic/admin/ticket/getTicketLogic.go @@ -3,11 +3,11 @@ package ticket import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/ticket/updateTicketStatusLogic.go b/internal/logic/admin/ticket/updateTicketStatusLogic.go index c205057..7897564 100644 --- a/internal/logic/admin/ticket/updateTicketStatusLogic.go +++ b/internal/logic/admin/ticket/updateTicketStatusLogic.go @@ -3,10 +3,10 @@ package ticket import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/tool/getSystemLogLogic.go b/internal/logic/admin/tool/getSystemLogLogic.go index e2d4660..760a60e 100644 --- a/internal/logic/admin/tool/getSystemLogLogic.go +++ b/internal/logic/admin/tool/getSystemLogLogic.go @@ -2,10 +2,14 @@ package tool import ( "context" + "encoding/json" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "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 GetSystemLogLogic struct { @@ -14,7 +18,7 @@ type GetSystemLogLogic struct { svcCtx *svc.ServiceContext } -// Get System Log +// NewGetSystemLogLogic Get System Log func NewGetSystemLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetSystemLogLogic { return &GetSystemLogLogic{ Logger: logger.WithContext(ctx), @@ -24,17 +28,22 @@ func NewGetSystemLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetS } func (l *GetSystemLogLogic) GetSystemLog() (resp *types.LogResponse, err error) { - //if l.svcCtx.Config.Debug { - // return nil, errors.Wrapf(xerr.NewErrCode(xerr.DebugModeError), "debug mode is enabled") - //} - //lines, err := logger.ReadLastNLines(l.svcCtx.Config.Logger.FilePath, 100) - //if err != nil { - // l.Errorw("[GetSystemLog]", logger.Field("error", "ReadLastNLines"), logger.Field(err)) - // return nil, err - //} - //logs := logger.ParseLog(lines) - //return &types.LogResponse{ - // List: logs, - //}, nil - return nil, nil + lines, err := logger.ReadLastNLines(l.svcCtx.Config.Logger.Path, 50) + if err != nil { + l.Error(err) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "get system log error: %v", err.Error()) + } + var list []map[string]interface{} + for _, line := range lines { + var log map[string]interface{} + if err = json.Unmarshal([]byte(line), &log); err != nil { + l.Error(err) + continue + } + list = append(list, log) + } + + return &types.LogResponse{ + List: list, + }, nil } diff --git a/internal/logic/admin/tool/getVersionLogic.go b/internal/logic/admin/tool/getVersionLogic.go new file mode 100644 index 0000000..a1a9acc --- /dev/null +++ b/internal/logic/admin/tool/getVersionLogic.go @@ -0,0 +1,51 @@ +package tool + +import ( + "context" + "fmt" + + "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" +) + +type GetVersionLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewGetVersionLogic Get Version +func NewGetVersionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetVersionLogic { + return &GetVersionLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetVersionLogic) GetVersion() (resp *types.VersionResponse, err error) { + version := constant.Version + buildTime := constant.BuildTime + + // Normalize unknown values + if version == "unknown version" { + version = "unknown" + } + if buildTime == "unknown time" { + buildTime = "unknown" + } + + // Format version based on whether it starts with 'v' + var formattedVersion string + if len(version) > 0 && version[0] == 'v' { + formattedVersion = fmt.Sprintf("%s(%s)", version[1:], buildTime) + } else { + formattedVersion = fmt.Sprintf("%s(%s) Develop", version, buildTime) + } + + return &types.VersionResponse{ + Version: formattedVersion, + }, nil +} diff --git a/internal/logic/admin/tool/restartSystemLogic.go b/internal/logic/admin/tool/restartSystemLogic.go index 929ae70..f5b0a8e 100644 --- a/internal/logic/admin/tool/restartSystemLogic.go +++ b/internal/logic/admin/tool/restartSystemLogic.go @@ -3,8 +3,8 @@ package tool import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/logger" ) type RestartSystemLogic struct { diff --git a/internal/logic/admin/user/batchDeleteUserLogic.go b/internal/logic/admin/user/batchDeleteUserLogic.go index c7781a1..d78376e 100644 --- a/internal/logic/admin/user/batchDeleteUserLogic.go +++ b/internal/logic/admin/user/batchDeleteUserLogic.go @@ -2,11 +2,14 @@ package user import ( "context" + "os" + "strings" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) @@ -25,6 +28,12 @@ func NewBatchDeleteUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *B } func (l *BatchDeleteUserLogic) BatchDeleteUser(req *types.BatchDeleteUserRequest) error { + isDemo := strings.ToLower(os.Getenv("PPANEL_MODE")) == "demo" + + if tool.Contains(req.Ids, 2) && isDemo { + return errors.Wrapf(xerr.NewErrCodeMsg(503, "Demo mode does not allow deletion of the admin user"), "BatchDeleteUser failed: cannot delete admin user in demo mode") + } + err := l.svcCtx.UserModel.BatchDeleteUser(l.ctx, req.Ids) if err != nil { l.Logger.Error("[BatchDeleteUserLogic] BatchDeleteUser failed: ", logger.Field("error", err.Error())) diff --git a/internal/logic/admin/user/createUserAuthMethodLogic.go b/internal/logic/admin/user/createUserAuthMethodLogic.go index a9bd1e1..d57a4f2 100644 --- a/internal/logic/admin/user/createUserAuthMethodLogic.go +++ b/internal/logic/admin/user/createUserAuthMethodLogic.go @@ -3,14 +3,14 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type CreateUserAuthMethodLogic struct { diff --git a/internal/logic/admin/user/createUserLogic.go b/internal/logic/admin/user/createUserLogic.go index 3cd97a4..5f6c858 100644 --- a/internal/logic/admin/user/createUserLogic.go +++ b/internal/logic/admin/user/createUserLogic.go @@ -5,13 +5,13 @@ import ( "fmt" "time" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/uuidx" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" ) @@ -39,10 +39,12 @@ func (l *CreateUserLogic) CreateUser(req *types.CreateUserRequest) error { } pwd := tool.EncodePassWord(req.Password) newUser := &user.User{ - Password: pwd, - ReferCode: req.ReferCode, - Balance: req.Balance, - IsAdmin: &req.IsAdmin, + Password: pwd, + ReferralPercentage: req.ReferralPercentage, + OnlyFirstPurchase: &req.OnlyFirstPurchase, + ReferCode: req.ReferCode, + Balance: req.Balance, + IsAdmin: &req.IsAdmin, } var ams []user.AuthMethods diff --git a/internal/logic/admin/user/createUserSubscribeLogic.go b/internal/logic/admin/user/createUserSubscribeLogic.go index dbaff74..08876f8 100644 --- a/internal/logic/admin/user/createUserSubscribeLogic.go +++ b/internal/logic/admin/user/createUserSubscribeLogic.go @@ -6,12 +6,12 @@ import ( "time" "github.com/google/uuid" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/uuidx" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) @@ -77,5 +77,9 @@ func (l *CreateUserSubscribeLogic) CreateUserSubscribe(req *types.CreateUserSubs return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "UpdateUserCache error: %v", err.Error()) } + err = l.svcCtx.SubscribeModel.ClearCache(l.ctx, userSub.SubscribeId) + if err != nil { + logger.Errorw("ClearSubscribe error", logger.Field("error", err.Error())) + } return nil } diff --git a/internal/logic/admin/user/currentUserLogic.go b/internal/logic/admin/user/currentUserLogic.go index 28b2727..adad6d6 100644 --- a/internal/logic/admin/user/currentUserLogic.go +++ b/internal/logic/admin/user/currentUserLogic.go @@ -3,15 +3,15 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/pkg/constant" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "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" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) diff --git a/internal/logic/admin/user/deleteUserAuthMethodLogic.go b/internal/logic/admin/user/deleteUserAuthMethodLogic.go index 38692ea..29dfcfe 100644 --- a/internal/logic/admin/user/deleteUserAuthMethodLogic.go +++ b/internal/logic/admin/user/deleteUserAuthMethodLogic.go @@ -3,10 +3,10 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/user/deleteUserDeviceLogic.go b/internal/logic/admin/user/deleteUserDeviceLogic.go index 101b33b..c92f79e 100644 --- a/internal/logic/admin/user/deleteUserDeviceLogic.go +++ b/internal/logic/admin/user/deleteUserDeviceLogic.go @@ -3,12 +3,12 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type DeleteUserDeviceLogic struct { diff --git a/internal/logic/admin/user/deleteUserLogic.go b/internal/logic/admin/user/deleteUserLogic.go index d283058..5253d09 100644 --- a/internal/logic/admin/user/deleteUserLogic.go +++ b/internal/logic/admin/user/deleteUserLogic.go @@ -2,11 +2,13 @@ package user import ( "context" + "os" + "strings" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) @@ -25,6 +27,11 @@ func NewDeleteUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Delete } func (l *DeleteUserLogic) DeleteUser(req *types.GetDetailRequest) error { + isDemo := strings.ToLower(os.Getenv("PPANEL_MODE")) == "demo" + + if req.Id == 2 && isDemo { + return errors.Wrapf(xerr.NewErrCodeMsg(503, "Demo mode does not allow deletion of the admin user"), "delete user failed: cannot delete admin user in demo mode") + } err := l.svcCtx.UserModel.Delete(l.ctx, req.Id) if err != nil { return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete user error: %v", err.Error()) diff --git a/internal/logic/admin/user/deleteUserSubscribeLogic.go b/internal/logic/admin/user/deleteUserSubscribeLogic.go index 463638e..397299d 100644 --- a/internal/logic/admin/user/deleteUserSubscribeLogic.go +++ b/internal/logic/admin/user/deleteUserSubscribeLogic.go @@ -3,10 +3,10 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) @@ -26,10 +26,27 @@ func NewDeleteUserSubscribeLogic(ctx context.Context, svcCtx *svc.ServiceContext } func (l *DeleteUserSubscribeLogic) DeleteUserSubscribe(req *types.DeleteUserSubscribeRequest) error { - err := l.svcCtx.UserModel.DeleteSubscribeById(l.ctx, req.UserSubscribeId) + // find user subscribe by ID + userSubscribe, err := l.svcCtx.UserModel.FindOneSubscribe(l.ctx, req.UserSubscribeId) + if err != nil { + l.Errorw("failed to find user subscribe", logger.Field("error", err.Error()), logger.Field("userSubscribeId", req.UserSubscribeId)) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to find user subscribe: %v", err.Error()) + } + + err = l.svcCtx.UserModel.DeleteSubscribeById(l.ctx, req.UserSubscribeId) if err != nil { l.Errorw("failed to delete user subscribe", logger.Field("error", err.Error()), logger.Field("userSubscribeId", req.UserSubscribeId)) return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "failed to delete user subscribe: %v", err.Error()) } + // Clear user subscribe cache + if err = l.svcCtx.UserModel.ClearSubscribeCache(l.ctx, userSubscribe); err != nil { + l.Errorw("failed to clear user subscribe cache", logger.Field("error", err.Error()), logger.Field("userSubscribeId", req.UserSubscribeId)) + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "failed to clear user subscribe cache: %v", err.Error()) + } + // Clear subscribe cache + if err = l.svcCtx.SubscribeModel.ClearCache(l.ctx, userSubscribe.SubscribeId); err != nil { + l.Errorw("failed to clear subscribe cache", logger.Field("error", err.Error()), logger.Field("subscribeId", userSubscribe.SubscribeId)) + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "failed to clear subscribe cache: %v", err.Error()) + } return nil } diff --git a/internal/logic/admin/user/getUserAuthMethodLogic.go b/internal/logic/admin/user/getUserAuthMethodLogic.go index a7f8740..aba4dfc 100644 --- a/internal/logic/admin/user/getUserAuthMethodLogic.go +++ b/internal/logic/admin/user/getUserAuthMethodLogic.go @@ -3,11 +3,11 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/user/getUserDetailLogic.go b/internal/logic/admin/user/getUserDetailLogic.go index ab4bfd7..11597d5 100644 --- a/internal/logic/admin/user/getUserDetailLogic.go +++ b/internal/logic/admin/user/getUserDetailLogic.go @@ -3,11 +3,11 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/user/getUserListLogic.go b/internal/logic/admin/user/getUserListLogic.go index 145cba9..3859f76 100644 --- a/internal/logic/admin/user/getUserListLogic.go +++ b/internal/logic/admin/user/getUserListLogic.go @@ -3,13 +3,13 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/phone" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/phone" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) @@ -32,6 +32,7 @@ func (l *GetUserListLogic) GetUserList(req *types.GetUserListRequest) (*types.Ge Search: req.Search, SubscribeId: req.SubscribeId, UserSubscribeId: req.UserSubscribeId, + Order: "DESC", }) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetUserListLogic failed: %v", err.Error()) @@ -40,20 +41,20 @@ func (l *GetUserListLogic) GetUserList(req *types.GetUserListRequest) (*types.Ge userRespList := make([]types.User, 0, len(list)) for _, item := range list { - var user types.User - tool.DeepCopy(&user, item) + var u types.User + tool.DeepCopy(&u, item) // 处理 AuthMethods - authMethods := make([]types.UserAuthMethod, len(user.AuthMethods)) // 直接创建目标 slice - for i, method := range user.AuthMethods { + authMethods := make([]types.UserAuthMethod, len(u.AuthMethods)) // 直接创建目标 slice + for i, method := range u.AuthMethods { tool.DeepCopy(&authMethods[i], method) if method.AuthType == "mobile" { authMethods[i].AuthIdentifier = phone.FormatToInternational(method.AuthIdentifier) } } - user.AuthMethods = authMethods + u.AuthMethods = authMethods - userRespList = append(userRespList, user) + userRespList = append(userRespList, u) } return &types.GetUserListResponse{ diff --git a/internal/logic/admin/user/getUserLoginLogsLogic.go b/internal/logic/admin/user/getUserLoginLogsLogic.go index 46ccc94..afaba61 100644 --- a/internal/logic/admin/user/getUserLoginLogsLogic.go +++ b/internal/logic/admin/user/getUserLoginLogsLogic.go @@ -3,12 +3,11 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/log" + "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" ) @@ -28,15 +27,34 @@ func NewGetUserLoginLogsLogic(ctx context.Context, svcCtx *svc.ServiceContext) * } func (l *GetUserLoginLogsLogic) GetUserLoginLogs(req *types.GetUserLoginLogsRequest) (resp *types.GetUserLoginLogsResponse, err error) { - data, total, err := l.svcCtx.UserModel.FilterLoginLogList(l.ctx, req.Page, req.Size, &user.LoginLogFilterParams{ - UserId: req.UserId, + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Type: log.TypeLogin.Uint8(), + ObjectID: req.UserId, }) if err != nil { l.Errorw("[GetUserLoginLogs] get user login logs failed", logger.Field("error", err.Error()), logger.Field("request", req)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get user login logs failed: %v", err.Error()) } var list []types.UserLoginLog - tool.DeepCopy(&list, data) + + for _, datum := range data { + var content log.Login + if err = content.Unmarshal([]byte(datum.Content)); err != nil { + l.Errorf("[GetUserLoginLogs] unmarshal login log content failed: %v", err.Error()) + continue + } + list = append(list, types.UserLoginLog{ + Id: datum.Id, + UserId: datum.ObjectID, + LoginIP: content.LoginIP, + UserAgent: content.UserAgent, + Success: content.Success, + Timestamp: datum.CreatedAt.UnixMilli(), + }) + } + return &types.GetUserLoginLogsResponse{ Total: total, List: list, diff --git a/internal/logic/admin/user/getUserSubscribeByIdLogic.go b/internal/logic/admin/user/getUserSubscribeByIdLogic.go index 6a4a454..2f9af88 100644 --- a/internal/logic/admin/user/getUserSubscribeByIdLogic.go +++ b/internal/logic/admin/user/getUserSubscribeByIdLogic.go @@ -3,11 +3,11 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/user/getUserSubscribeDevicesLogic.go b/internal/logic/admin/user/getUserSubscribeDevicesLogic.go index 349152f..6d84f65 100644 --- a/internal/logic/admin/user/getUserSubscribeDevicesLogic.go +++ b/internal/logic/admin/user/getUserSubscribeDevicesLogic.go @@ -3,13 +3,13 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type GetUserSubscribeDevicesLogic struct { diff --git a/internal/logic/admin/user/getUserSubscribeLogic.go b/internal/logic/admin/user/getUserSubscribeLogic.go index abf8dfc..2deb3ac 100644 --- a/internal/logic/admin/user/getUserSubscribeLogic.go +++ b/internal/logic/admin/user/getUserSubscribeLogic.go @@ -3,11 +3,11 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/user/getUserSubscribeLogsLogic.go b/internal/logic/admin/user/getUserSubscribeLogsLogic.go index 01b8135..ca33355 100644 --- a/internal/logic/admin/user/getUserSubscribeLogsLogic.go +++ b/internal/logic/admin/user/getUserSubscribeLogsLogic.go @@ -3,12 +3,12 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/log" + "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" ) @@ -28,10 +28,7 @@ func NewGetUserSubscribeLogsLogic(ctx context.Context, svcCtx *svc.ServiceContex } func (l *GetUserSubscribeLogsLogic) GetUserSubscribeLogs(req *types.GetUserSubscribeLogsRequest) (resp *types.GetUserSubscribeLogsResponse, err error) { - data, total, err := l.svcCtx.UserModel.FilterSubscribeLogList(l.ctx, req.Page, req.Size, &user.SubscribeLogFilterParams{ - UserSubscribeId: req.SubscribeId, - UserId: req.UserId, - }) + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{}) if err != nil { l.Errorw("[GetUserSubscribeLogs] Get User Subscribe Logs Error:", logger.Field("err", err.Error())) diff --git a/internal/logic/admin/user/getUserSubscribeResetTrafficLogsLogic.go b/internal/logic/admin/user/getUserSubscribeResetTrafficLogsLogic.go new file mode 100644 index 0000000..fb01d01 --- /dev/null +++ b/internal/logic/admin/user/getUserSubscribeResetTrafficLogsLogic.go @@ -0,0 +1,62 @@ +package user + +import ( + "context" + + "github.com/perfect-panel/server/internal/model/log" + "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 GetUserSubscribeResetTrafficLogsLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// Get user subcribe reset traffic logs +func NewGetUserSubscribeResetTrafficLogsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserSubscribeResetTrafficLogsLogic { + return &GetUserSubscribeResetTrafficLogsLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetUserSubscribeResetTrafficLogsLogic) GetUserSubscribeResetTrafficLogs(req *types.GetUserSubscribeResetTrafficLogsRequest) (resp *types.GetUserSubscribeResetTrafficLogsResponse, err error) { + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Type: log.TypeResetSubscribe.Uint8(), + ObjectID: req.UserSubscribeId, + }) + if err != nil { + l.Errorf("[ResetSubscribeTrafficLog] failed to filter system log: %v", err) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FilterSystemLog failed, err: %v", err) + } + + var list []types.ResetSubscribeTrafficLog + + for _, item := range data { + var content log.ResetSubscribe + if err = content.Unmarshal([]byte(item.Content)); err != nil { + l.Errorf("[ResetSubscribeTrafficLog] failed to unmarshal log: %v", err) + continue + } + list = append(list, types.ResetSubscribeTrafficLog{ + Id: item.Id, + Type: content.Type, + OrderNo: content.OrderNo, + Timestamp: content.Timestamp, + UserSubscribeId: item.ObjectID, + }) + } + + return &types.GetUserSubscribeResetTrafficLogsResponse{ + Total: total, + List: list, + }, nil +} diff --git a/internal/logic/admin/user/getUserSubscribeTrafficLogsLogic.go b/internal/logic/admin/user/getUserSubscribeTrafficLogsLogic.go index 782b96f..07f0055 100644 --- a/internal/logic/admin/user/getUserSubscribeTrafficLogsLogic.go +++ b/internal/logic/admin/user/getUserSubscribeTrafficLogsLogic.go @@ -3,13 +3,13 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type GetUserSubscribeTrafficLogsLogic struct { diff --git a/internal/logic/admin/user/kickOfflineByUserDeviceLogic.go b/internal/logic/admin/user/kickOfflineByUserDeviceLogic.go index dafec89..9c4ec82 100644 --- a/internal/logic/admin/user/kickOfflineByUserDeviceLogic.go +++ b/internal/logic/admin/user/kickOfflineByUserDeviceLogic.go @@ -3,10 +3,10 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/user/updateUserAuthMethodLogic.go b/internal/logic/admin/user/updateUserAuthMethodLogic.go index c97d466..6b87523 100644 --- a/internal/logic/admin/user/updateUserAuthMethodLogic.go +++ b/internal/logic/admin/user/updateUserAuthMethodLogic.go @@ -3,10 +3,10 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) @@ -31,11 +31,21 @@ func (l *UpdateUserAuthMethodLogic) UpdateUserAuthMethod(req *types.UpdateUserAu l.Errorw("Get user auth method error", logger.Field("error", err.Error()), logger.Field("userId", req.UserId), logger.Field("authType", req.AuthType)) return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Get user auth method error: %v", err.Error()) } + userInfo, err := l.svcCtx.UserModel.FindOne(l.ctx, req.UserId) + if err != nil { + l.Errorw("Get user info error", logger.Field("error", err.Error()), logger.Field("userId", req.UserId)) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Get user info error: %v", err.Error()) + } + method.AuthType = req.AuthType method.AuthIdentifier = req.AuthIdentifier if err = l.svcCtx.UserModel.UpdateUserAuthMethods(l.ctx, method); err != nil { l.Errorw("Update user auth method error", logger.Field("error", err.Error()), logger.Field("userId", req.UserId), logger.Field("authType", req.AuthType)) return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "Update user auth method error: %v", err.Error()) } + if err = l.svcCtx.UserModel.UpdateUserCache(l.ctx, userInfo); err != nil { + l.Errorw("Update user cache error", logger.Field("error", err.Error()), logger.Field("userId", req.UserId)) + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Update user cache error: %v", err.Error()) + } return nil } diff --git a/internal/logic/admin/user/updateUserBasicInfoLogic.go b/internal/logic/admin/user/updateUserBasicInfoLogic.go index 5f3d8a9..9f57f75 100644 --- a/internal/logic/admin/user/updateUserBasicInfoLogic.go +++ b/internal/logic/admin/user/updateUserBasicInfoLogic.go @@ -2,12 +2,16 @@ package user import ( "context" + "os" + "strings" + "time" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/log" + "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" ) @@ -33,12 +37,97 @@ func (l *UpdateUserBasicInfoLogic) UpdateUserBasicInfo(req *types.UpdateUserBasi return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Find User Error") } - tool.DeepCopy(userInfo, req) + isDemo := strings.ToLower(os.Getenv("PPANEL_MODE")) == "demo" + if req.Avatar != "" && !tool.IsValidImageSize(req.Avatar, 1024) { return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Invalid Image Size") } + + if userInfo.Balance != req.Balance { + change := req.Balance - userInfo.Balance + balanceLog := log.Balance{ + Type: log.BalanceTypeAdjust, + Amount: change, + OrderNo: "", + Balance: req.Balance, + Timestamp: time.Now().UnixMilli(), + } + content, _ := balanceLog.Marshal() + + err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{ + Type: log.TypeBalance.Uint8(), + Date: time.Now().Format(time.DateOnly), + ObjectID: userInfo.Id, + Content: string(content), + }) + if err != nil { + l.Errorw("[UpdateUserBasicInfoLogic] Insert Balance Log Error:", logger.Field("err", err.Error()), logger.Field("userId", req.UserId)) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "Insert Balance Log Error") + } + userInfo.Balance = req.Balance + } + + if userInfo.GiftAmount != req.GiftAmount { + change := req.GiftAmount - userInfo.GiftAmount + if change != 0 { + var changeType uint16 + if userInfo.GiftAmount < req.GiftAmount { + changeType = log.GiftTypeIncrease + } else { + changeType = log.GiftTypeReduce + } + giftLog := log.Gift{ + Type: changeType, + Amount: change, + Balance: req.GiftAmount, + Remark: "Admin adjustment", + Timestamp: time.Now().UnixMilli(), + } + content, _ := giftLog.Marshal() + // Add gift amount change log + err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{ + Type: log.TypeGift.Uint8(), + Date: time.Now().Format(time.DateOnly), + ObjectID: userInfo.Id, + Content: string(content), + }) + if err != nil { + l.Errorw("[UpdateUserBasicInfoLogic] Insert Balance Log Error:", logger.Field("err", err.Error()), logger.Field("userId", req.UserId)) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "Insert Balance Log Error") + } + userInfo.GiftAmount = req.GiftAmount + } + } + + if req.Commission != userInfo.Commission { + + commentLog := log.Commission{ + Type: log.CommissionTypeAdjust, + Amount: req.Commission - userInfo.Commission, + Timestamp: time.Now().UnixMilli(), + } + + content, _ := commentLog.Marshal() + err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{ + Type: log.TypeCommission.Uint8(), + Date: time.Now().Format(time.DateOnly), + ObjectID: userInfo.Id, + Content: string(content), + }) + if err != nil { + l.Errorw("[UpdateUserBasicInfoLogic] Insert Commission Log Error:", logger.Field("err", err.Error()), logger.Field("userId", req.UserId)) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "Insert Commission Log Error") + } + userInfo.Commission = req.Commission + } + tool.DeepCopy(userInfo, req) + userInfo.OnlyFirstPurchase = &req.OnlyFirstPurchase + userInfo.ReferralPercentage = req.ReferralPercentage + if req.Password != "" { - l.Infow("[UpdateUserBasicInfoLogic] Update User Password:", logger.Field("userId", req.UserId), logger.Field("password", req.Password)) + if userInfo.Id == 2 && isDemo { + 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) } diff --git a/internal/logic/admin/user/updateUserDeviceLogic.go b/internal/logic/admin/user/updateUserDeviceLogic.go index ecffd39..40ff9b2 100644 --- a/internal/logic/admin/user/updateUserDeviceLogic.go +++ b/internal/logic/admin/user/updateUserDeviceLogic.go @@ -3,10 +3,10 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/user/updateUserNotifySettingLogic.go b/internal/logic/admin/user/updateUserNotifySettingLogic.go index fc0142b..18673e1 100644 --- a/internal/logic/admin/user/updateUserNotifySettingLogic.go +++ b/internal/logic/admin/user/updateUserNotifySettingLogic.go @@ -3,11 +3,11 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/admin/user/updateUserSubscribeLogic.go b/internal/logic/admin/user/updateUserSubscribeLogic.go index 00b7020..9d92ce5 100644 --- a/internal/logic/admin/user/updateUserSubscribeLogic.go +++ b/internal/logic/admin/user/updateUserSubscribeLogic.go @@ -4,11 +4,11 @@ import ( "context" "time" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/xerr" "github.com/pkg/errors" ) @@ -28,13 +28,20 @@ func NewUpdateUserSubscribeLogic(ctx context.Context, svcCtx *svc.ServiceContext } func (l *UpdateUserSubscribeLogic) UpdateUserSubscribe(req *types.UpdateUserSubscribeRequest) error { - userSub, err := l.svcCtx.UserModel.FindOneUserSubscribe(l.ctx, req.UserSubscribeId) + userSub, err := l.svcCtx.UserModel.FindOneSubscribe(l.ctx, req.UserSubscribeId) if err != nil { l.Errorw("FindOneUserSubscribe failed:", logger.Field("error", err.Error()), logger.Field("userSubscribeId", req.UserSubscribeId)) return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindOneUserSubscribe failed: %v", err.Error()) } + expiredAt := time.UnixMilli(req.ExpiredAt) + if time.Since(expiredAt).Minutes() > 0 { + userSub.Status = 3 + } else { + userSub.Status = 1 + } + err = l.svcCtx.UserModel.UpdateSubscribe(l.ctx, &user.Subscribe{ - Id: req.UserSubscribeId, + Id: userSub.Id, UserId: userSub.UserId, OrderId: userSub.OrderId, SubscribeId: req.SubscribeId, @@ -52,6 +59,15 @@ func (l *UpdateUserSubscribeLogic) UpdateUserSubscribe(req *types.UpdateUserSubs l.Errorw("UpdateSubscribe failed:", logger.Field("error", err.Error())) return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "UpdateSubscribe failed: %v", err.Error()) } - + // Clear user subscribe cache + if err = l.svcCtx.UserModel.ClearSubscribeCache(l.ctx, userSub); 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 subscribe cache + if err = l.svcCtx.SubscribeModel.ClearCache(l.ctx, userSub.SubscribeId); err != nil { + l.Errorw("failed to clear subscribe cache", logger.Field("error", err.Error()), logger.Field("subscribeId", userSub.SubscribeId)) + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "failed to clear subscribe cache: %v", err.Error()) + } return nil } diff --git a/internal/logic/app/announcement/queryAnnouncementLogic.go b/internal/logic/app/announcement/queryAnnouncementLogic.go deleted file mode 100644 index 01c771c..0000000 --- a/internal/logic/app/announcement/queryAnnouncementLogic.go +++ /dev/null @@ -1,47 +0,0 @@ -package announcement - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/model/announcement" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -type QueryAnnouncementLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// NewQueryAnnouncementLogic Query announcement -func NewQueryAnnouncementLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryAnnouncementLogic { - return &QueryAnnouncementLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *QueryAnnouncementLogic) QueryAnnouncement(req *types.QueryAnnouncementRequest) (resp *types.QueryAnnouncementResponse, err error) { - enable := true - total, list, err := l.svcCtx.AnnouncementModel.GetAnnouncementListByPage(l.ctx, req.Page, req.Size, announcement.Filter{ - Show: &enable, - Pinned: req.Pinned, - Popup: req.Popup, - }) - if err != nil { - l.Error("[QueryAnnouncementLogic] GetAnnouncementListByPage error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetAnnouncementListByPage error: %v", err.Error()) - } - resp = &types.QueryAnnouncementResponse{} - resp.Total = total - resp.List = make([]types.Announcement, 0) - tool.DeepCopy(&resp.List, list) - return -} diff --git a/internal/logic/app/auth/checkLogic.go b/internal/logic/app/auth/checkLogic.go deleted file mode 100644 index 3247084..0000000 --- a/internal/logic/app/auth/checkLogic.go +++ /dev/null @@ -1,41 +0,0 @@ -package auth - -import ( - "context" - - "github.com/pkg/errors" - "gorm.io/gorm" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -type CheckLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Check Account -func NewCheckLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CheckLogic { - return &CheckLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *CheckLogic) Check(req *types.AppAuthCheckRequest) (resp *types.AppAuthCheckResponse, err error) { - resp = &types.AppAuthCheckResponse{} - _, err = findUserByMethod(l.ctx, l.svcCtx, req.Method, req.Identifier, req.Account, req.AreaCode) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - resp.Status = false - return resp, nil - } - return resp, err - } - resp.Status = true - return -} diff --git a/internal/logic/app/auth/findUserByMethod.go b/internal/logic/app/auth/findUserByMethod.go deleted file mode 100644 index b5a0b6c..0000000 --- a/internal/logic/app/auth/findUserByMethod.go +++ /dev/null @@ -1,59 +0,0 @@ -package auth - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/pkg/authmethod" - - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/phone" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" -) - -func findUserByMethod(ctx context.Context, svcCtx *svc.ServiceContext, method, identifier, account, areaCode string) (userInfo *user.User, err error) { - var authMethods *user.AuthMethods - switch method { - case authmethod.Email: - authMethods, err = svcCtx.UserModel.FindUserAuthMethodByOpenID(ctx, authmethod.Email, account) - case authmethod.Mobile: - phoneNumber, err := phone.FormatToE164(areaCode, account) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.TelephoneError), "Invalid phone number") - } - authMethods, err = svcCtx.UserModel.FindUserAuthMethodByOpenID(ctx, authmethod.Mobile, phoneNumber) - if err != nil { - return nil, err - } - case authmethod.Device: - userDevice, err := svcCtx.UserModel.FindOneDeviceByIdentifier(ctx, identifier) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, err - } - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query user device imei error") - } - return svcCtx.UserModel.FindOne(ctx, userDevice.UserId) - default: - return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "unknown method") - } - if err != nil { - return nil, err - } - return svcCtx.UserModel.FindOne(ctx, authMethods.UserId) -} - -func existError(method string) error { - switch method { - case authmethod.Email: - return errors.Wrapf(xerr.NewErrCode(xerr.EmailExist), "") - case authmethod.Mobile: - return errors.Wrapf(xerr.NewErrCode(xerr.TelephoneExist), "") - case authmethod.Device: - return errors.Wrapf(xerr.NewErrCode(xerr.DeviceExist), "") - default: - return errors.New("unknown method") - } -} diff --git a/internal/logic/app/auth/getAppConfigLogic.go b/internal/logic/app/auth/getAppConfigLogic.go deleted file mode 100644 index d5434d3..0000000 --- a/internal/logic/app/auth/getAppConfigLogic.go +++ /dev/null @@ -1,136 +0,0 @@ -package auth - -import ( - "context" - "encoding/json" - "fmt" - "strings" - - "github.com/perfect-panel/ppanel-server/internal/model/application" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -type GetAppConfigLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// GetAppConfig -func NewGetAppConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAppConfigLogic { - return &GetAppConfigLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *GetAppConfigLogic) GetAppConfig(req *types.AppConfigRequest) (resp *types.AppConfigResponse, err error) { - resp = &types.AppConfigResponse{} - systems, err := l.svcCtx.SystemModel.GetSiteConfig(l.ctx) - if err != nil { - l.Errorw("[QueryApplicationConfig] GetSiteConfig error: ", logger.Field("error", err.Error())) - } - for _, sysVal := range systems { - if sysVal.Key == "CustomData" { - jsonStr := strings.ReplaceAll(sysVal.Value, "\\", "") - customData := make(map[string]interface{}) - if err = json.Unmarshal([]byte(jsonStr), &customData); err != nil { - break - } - - website := customData["website"] - if website != nil { - resp.OfficialWebsite = fmt.Sprintf("%v", website) - } - krWebsiteId := customData["kr_website_id"] - if krWebsiteId != nil { - resp.KrWebsiteId = fmt.Sprintf("%v", krWebsiteId) - } - invitationLink := customData["invitation_link"] - if krWebsiteId != nil { - resp.InvitationLink = fmt.Sprintf("%v", invitationLink) - } - - versionReview := customData["version_review"] - if versionReview != nil && req.UserAgent == "ios" { - resp.Application.VersionReview = fmt.Sprintf("%v", versionReview) - } - - contacts := customData["contacts"] - if contacts != nil { - contactsJson, err := json.Marshal(contacts) - if err == nil { - contactsMap := make(map[string]string) - err = json.Unmarshal(contactsJson, &contactsMap) - if err == nil { - resp.OfficialEmail = fmt.Sprintf("%v", contactsMap["email"]) - resp.OfficialTelegram = fmt.Sprintf("%v", contactsMap["telegram"]) - resp.OfficialTelephone = fmt.Sprintf("%v", contactsMap["telephone"]) - } - } - } - break - } - } - - var applications []*application.Application - err = l.svcCtx.ApplicationModel.Transaction(l.ctx, func(tx *gorm.DB) (err error) { - return tx.Model(applications).Preload("ApplicationVersions").Find(&applications).Error - }) - if err != nil { - l.Errorw("[QueryApplicationConfig] get application error: ", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get application error: %v", err.Error()) - } - - if len(applications) == 0 { - return resp, nil - } - - isOk := false - for _, app := range applications { - if isOk { - break - } - resp.Application.Name = app.Name - resp.Application.Description = app.Description - applicationVersions := app.ApplicationVersions - if len(applicationVersions) != 0 { - for _, applicationVersion := range applicationVersions { - if applicationVersion.Platform == req.UserAgent { - resp.Application.Id = applicationVersion.ApplicationId - resp.Application.Url = applicationVersion.Url - resp.Application.Version = applicationVersion.Version - resp.Application.VersionDescription = applicationVersion.Description - resp.Application.IsDefault = applicationVersion.IsDefault - isOk = true - break - } - } - } - } - - configs, err := l.svcCtx.ApplicationModel.FindOneConfig(l.ctx, 1) - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - l.Logger.Error("[GetAppInfo] FindOneAppConfig error: ", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetAppInfo FindOneAppConfig error: %v", err.Error()) - } - resp.EncryptionKey = configs.EncryptionKey - resp.EncryptionMethod = configs.EncryptionMethod - resp.Domains = strings.Split(configs.Domains, ";") - resp.StartupPicture = configs.StartupPicture - resp.StartupPictureSkipTime = configs.StartupPictureSkipTime - if configs.InvitationLink != "" { - resp.InvitationLink = configs.InvitationLink - } - if configs.KrWebsiteId != "" { - resp.KrWebsiteId = configs.KrWebsiteId - } - return -} diff --git a/internal/logic/app/auth/loginLogic.go b/internal/logic/app/auth/loginLogic.go deleted file mode 100644 index e70905d..0000000 --- a/internal/logic/app/auth/loginLogic.go +++ /dev/null @@ -1,194 +0,0 @@ -package auth - -import ( - "encoding/json" - "fmt" - "time" - - "github.com/perfect-panel/ppanel-server/pkg/authmethod" - - "github.com/gin-gonic/gin" - - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/phone" - - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/logic/common" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/jwt" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" -) - -type LoginLogic struct { - logger.Logger - ctx *gin.Context - svcCtx *svc.ServiceContext -} - -// Login -func NewLoginLogic(ctx *gin.Context, svcCtx *svc.ServiceContext) *LoginLogic { - return &LoginLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *LoginLogic) Login(req *types.AppAuthRequest) (resp *types.AppAuthRespone, err error) { - - loginStatus := false - var userInfo *user.User - // Record login status - defer func(svcCtx *svc.ServiceContext) { - if userInfo != nil && userInfo.Id != 0 { - if err := svcCtx.UserModel.InsertLoginLog(l.ctx, &user.LoginLog{ - UserId: userInfo.Id, - LoginIP: l.ctx.ClientIP(), - UserAgent: l.ctx.Request.UserAgent(), - Success: &loginStatus, - }); err != nil { - l.Errorw("InsertLoginLog Error", logger.Field("error", err.Error())) - } - } - }(l.svcCtx) - - resp = &types.AppAuthRespone{} - //query user - userInfo, err = findUserByMethod(l.ctx, l.svcCtx, req.Method, req.Identifier, req.Account, req.AreaCode) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password") - } - return resp, err - } - - switch req.Method { - case authmethod.Email: - - if !l.svcCtx.Config.Email.Enable { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.EmailNotEnabled), "Email function is not enabled yet") - } - - if req.Code != "" { - cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, constant.Security.String(), req.Account) - value, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result() - if err != nil { - l.Errorw("Redis Error", logger.Field("error", err.Error()), logger.Field("cacheKey", cacheKey)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - - var payload common.CacheKeyPayload - err = json.Unmarshal([]byte(value), &payload) - if err != nil { - l.Errorw("Unmarshal Error", logger.Field("error", err.Error()), logger.Field("value", value)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - - if payload.Code != req.Code { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - l.svcCtx.Redis.Del(l.ctx, cacheKey) - } else { - // Verify password - if !tool.VerifyPassWord(req.Password, userInfo.Password) { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password") - } - } - case authmethod.Mobile: - if !l.svcCtx.Config.Mobile.Enable { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SmsNotEnabled), "sms login is not enabled") - } - phoneNumber, err := phone.FormatToE164(req.AreaCode, req.Account) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.TelephoneError), "Invalid phone number") - } - - if req.Code != "" { - cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeTelephoneCacheKey, constant.Security, phoneNumber) - value, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result() - if err != nil { - l.Errorw("Redis Error", logger.Field("error", err.Error()), logger.Field("cacheKey", cacheKey)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - - if value == "" { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - - var payload common.CacheKeyPayload - if err := json.Unmarshal([]byte(value), &payload); err != nil { - l.Errorw("[SendSmsCode]: Unmarshal Error", logger.Field("error", err.Error()), logger.Field("value", value)) - } - if payload.Code != req.Code { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - l.svcCtx.Redis.Del(l.ctx, cacheKey) - } else { - // Verify password - if !tool.VerifyPassWord(req.Password, userInfo.Password) { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password") - } - } - case authmethod.Device: - default: - return nil, existError(req.Method) - } - - device, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, req.Identifier) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - if req.Method == authmethod.Device { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "device not exist") - } - //Add User Device - userInfo.UserDevices = append(userInfo.UserDevices, user.Device{ - UserAgent: req.UserAgent, - Identifier: req.Identifier, - Ip: l.ctx.ClientIP(), - }) - err = l.svcCtx.UserModel.Update(l.ctx, userInfo) - if err != nil { - l.Errorw("[UpdateUserBindDevice] Fail", logger.Field("error", err.Error())) - } - } - } else { - //Change the user who owns the device - if device.UserId != userInfo.Id { - device.UserId = userInfo.Id - } - device.Ip = l.ctx.ClientIP() - err = l.svcCtx.UserModel.UpdateDevice(l.ctx, device) - if err != nil { - l.Errorw("[UpdateUserBindDevice] Fail", logger.Field("error", 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), - ) - if err != nil { - l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "token generate error: %v", err.Error()) - } - 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 { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "set session id error: %v", err.Error()) - } - - resp.Token = token - return -} diff --git a/internal/logic/app/auth/registerLogic.go b/internal/logic/app/auth/registerLogic.go deleted file mode 100644 index cb047b3..0000000 --- a/internal/logic/app/auth/registerLogic.go +++ /dev/null @@ -1,249 +0,0 @@ -package auth - -import ( - "encoding/json" - "fmt" - "time" - - "github.com/perfect-panel/ppanel-server/pkg/authmethod" - - "github.com/gin-gonic/gin" - - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/logic/common" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/jwt" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/phone" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" -) - -type CacheKeyPayload struct { - Code string `json:"code"` - LastAt int64 `json:"lastAt"` -} -type RegisterLogic struct { - logger.Logger - ctx *gin.Context - svcCtx *svc.ServiceContext -} - -// Register -func NewRegisterLogic(ctx *gin.Context, svcCtx *svc.ServiceContext) *RegisterLogic { - return &RegisterLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *RegisterLogic) Register(req *types.AppAuthRequest) (resp *types.AppAuthRespone, err error) { - resp = &types.AppAuthRespone{} - var referer *user.User - c := l.svcCtx.Config.Register - // Check if the registration is stopped - if c.StopRegister { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.StopRegister), "stop register") - } - - if req.Invite == "" { - if l.svcCtx.Config.Invite.ForcedInvite { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.InviteCodeError), "invite code is required") - } - } else { - // Check if the invite code is valid - referer, err = l.svcCtx.UserModel.FindOneByReferCode(l.ctx, req.Invite) - if err != nil { - l.Errorw("FindOneByReferCode Error", logger.Field("error", err)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.InviteCodeError), "invite code is invalid") - } - } - - if req.Password == "" { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.PasswordIsEmpty), "Password required") - } - - userInfo, err := findUserByMethod(l.ctx, l.svcCtx, req.Method, req.Identifier, req.Account, req.AreaCode) - if err == nil && userInfo != nil { - return nil, existError(req.Method) - } - if !errors.Is(err, gorm.ErrRecordNotFound) { - return nil, err - } - // Generate password - pwd := tool.EncodePassWord(req.Password) - userInfo = &user.User{ - Password: pwd, - } - if referer != nil { - userInfo.RefererId = referer.Id - } - switch req.Method { - case authmethod.Email: - if !l.svcCtx.Config.Email.Enable { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.EmailNotEnabled), "Email function is not enabled yet") - } - if l.svcCtx.Config.Email.EnableVerify { - cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, constant.Register.String(), req.Account) - value, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result() - if err != nil { - l.Errorw("Redis Error", logger.Field("error", err.Error()), logger.Field("cacheKey", cacheKey)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - - var payload common.CacheKeyPayload - err = json.Unmarshal([]byte(value), &payload) - if err != nil { - l.Errorw("Unmarshal Error", logger.Field("error", err.Error()), logger.Field("value", value)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - - if payload.Code != req.Code { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - } - userInfo.AuthMethods = []user.AuthMethods{{ - AuthType: authmethod.Email, - AuthIdentifier: req.Account, - }} - - case authmethod.Mobile: - if req.AreaCode == "" { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.TelephoneAreaCodeIsEmpty), "area code required") - } - - if !l.svcCtx.Config.Mobile.Enable { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SmsNotEnabled), "sms login is not enabled") - } - phoneNumber, err := phone.FormatToE164(req.AreaCode, req.Account) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.TelephoneError), "Invalid phone number") - } - cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeTelephoneCacheKey, constant.Register, phoneNumber) - value, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result() - if err != nil { - l.Errorw("Redis Error", logger.Field("error", err.Error()), logger.Field("cacheKey", cacheKey)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - - var payload CacheKeyPayload - _ = json.Unmarshal([]byte(value), &payload) - if payload.Code != req.Code { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - userInfo.AuthMethods = []user.AuthMethods{{ - AuthType: authmethod.Mobile, - AuthIdentifier: phoneNumber, - Verified: true, - }} - case authmethod.Device: - oneDevice, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, req.Identifier) - if err == nil && oneDevice != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DeviceExist), "device exist") - } - default: - return nil, existError(req.Method) - } - - device, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, req.Identifier) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - //Add User Device - userInfo.UserDevices = append(userInfo.UserDevices, user.Device{ - Ip: l.ctx.ClientIP(), - Identifier: req.Identifier, - UserAgent: req.UserAgent, - }) - } else { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query user info failed: %v", err.Error()) - } - } else { - //Delete Other User Device - err = l.svcCtx.UserModel.DeleteDevice(l.ctx, device.Id) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "delete old user device failed: %v", err.Error()) - } else { - //User Add Device - userInfo.UserDevices = append(userInfo.UserDevices, user.Device{ - Ip: l.ctx.ClientIP(), - Identifier: req.Identifier, - UserAgent: req.UserAgent, - }) - } - } - - err = l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { - // Save user information - if err := db.Create(userInfo).Error; err != nil { - return err - } - // Generate ReferCode - userInfo.ReferCode = uuidx.UserInviteCode(userInfo.Id) - // Update ReferCode - if err := db.Model(&user.User{}).Where("id = ?", userInfo.Id).Update("refer_code", userInfo.ReferCode).Error; err != nil { - return err - } - if l.svcCtx.Config.Register.EnableTrial { - // Active trial - if err = l.activeTrial(userInfo.Id); err != nil { - return err - } - } - return nil - }) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert user info 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), - ) - if err != nil { - l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "token generate error: %v", err.Error()) - } - - 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 { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "set session id error: %v", err.Error()) - } - - resp.Token = token - return -} - -func (l *RegisterLogic) activeTrial(uid int64) error { - sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, l.svcCtx.Config.Register.TrialSubscribe) - if err != nil { - return err - } - userSub := &user.Subscribe{ - Id: 0, - UserId: uid, - OrderId: 0, - SubscribeId: sub.Id, - StartTime: time.Now(), - ExpireTime: tool.AddTime(l.svcCtx.Config.Register.TrialTimeUnit, l.svcCtx.Config.Register.TrialTime, time.Now()), - Traffic: sub.Traffic, - Download: 0, - Upload: 0, - Token: uuidx.SubscribeToken(fmt.Sprintf("Trial-%v", uid)), - UUID: uuidx.NewUUID().String(), - Status: 1, - } - return l.svcCtx.UserModel.InsertSubscribe(l.ctx, userSub) -} diff --git a/internal/logic/app/auth/resetPasswordLogic.go b/internal/logic/app/auth/resetPasswordLogic.go deleted file mode 100644 index 21a7a5b..0000000 --- a/internal/logic/app/auth/resetPasswordLogic.go +++ /dev/null @@ -1,161 +0,0 @@ -package auth - -import ( - "encoding/json" - "fmt" - "time" - - "github.com/perfect-panel/ppanel-server/pkg/authmethod" - - "github.com/gin-gonic/gin" - - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/phone" - - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/logic/common" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/jwt" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" -) - -type ResetPasswordLogic struct { - logger.Logger - ctx *gin.Context - svcCtx *svc.ServiceContext -} - -// Reset Password -func NewResetPasswordLogic(ctx *gin.Context, svcCtx *svc.ServiceContext) *ResetPasswordLogic { - return &ResetPasswordLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *ResetPasswordLogic) ResetPassword(req *types.AppAuthRequest) (resp *types.AppAuthRespone, err error) { - resp = &types.AppAuthRespone{} - userInfo, err := findUserByMethod(l.ctx, l.svcCtx, req.Method, req.Identifier, req.Account, req.AreaCode) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "query user info failed") - } - l.Errorw("FindOneByEmail Error", logger.Field("error", err)) - return nil, err - } - - switch req.Method { - case authmethod.Mobile: - if !l.svcCtx.Config.Mobile.Enable { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SmsNotEnabled), "sms login is not enabled") - } - phoneNumber, err := phone.FormatToE164(req.AreaCode, req.Account) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.TelephoneError), "Invalid phone number") - } - cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeTelephoneCacheKey, constant.Security, phoneNumber) - value, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result() - if err != nil { - l.Errorw("Redis Error", logger.Field("error", err.Error()), logger.Field("cacheKey", cacheKey)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - - var payload common.CacheKeyPayload - err = json.Unmarshal([]byte(value), &payload) - if err != nil { - l.Errorw("Unmarshal Error", logger.Field("error", err.Error()), logger.Field("value", value)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - - if payload.Code != req.Code { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - case authmethod.Email: - if !l.svcCtx.Config.Email.Enable { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.EmailNotEnabled), "Email function is not enabled yet") - } - - cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, constant.Security.String(), req.Account) - value, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result() - if err != nil { - l.Errorw("Redis Error", logger.Field("error", err.Error()), logger.Field("cacheKey", cacheKey)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - - var payload CacheKeyPayload - err = json.Unmarshal([]byte(value), &payload) - if err != nil { - l.Errorw("Unmarshal Error", logger.Field("error", err.Error()), logger.Field("value", value)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - - if payload.Code != req.Code { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - default: - return nil, errors.New("unknown method") - } - - userInfo.Password = tool.EncodePassWord(req.Password) - err = l.svcCtx.UserModel.Update(l.ctx, userInfo) - if err != nil { - l.Errorw("UpdateUser Error", logger.Field("error", err)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update user info failed: %v", err.Error()) - } - - device, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, req.Identifier) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - //Add User Device - userInfo.UserDevices = append(userInfo.UserDevices, user.Device{ - Ip: l.ctx.ClientIP(), - Identifier: req.Identifier, - UserAgent: req.UserAgent, - }) - } else { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query user info failed: %v", err.Error()) - } - } else { - if device.UserId != userInfo.Id { - //Change the user who owns the device - if device.UserId != userInfo.Id { - device.UserId = userInfo.Id - } - device.Ip = l.ctx.ClientIP() - err = l.svcCtx.UserModel.UpdateDevice(l.ctx, device) - if err != nil { - l.Errorw("[UpdateUserBindDevice] Fail", logger.Field("error", 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), - ) - if err != nil { - l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "token generate error: %v", err.Error()) - } - - 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 { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "set session id error: %v", err.Error()) - } - resp.Token = token - return -} diff --git a/internal/logic/app/document/queryDocumentDetailLogic.go b/internal/logic/app/document/queryDocumentDetailLogic.go deleted file mode 100644 index f049042..0000000 --- a/internal/logic/app/document/queryDocumentDetailLogic.go +++ /dev/null @@ -1,39 +0,0 @@ -package document - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type QueryDocumentDetailLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// NewQueryDocumentDetailLogic Get document detail -func NewQueryDocumentDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryDocumentDetailLogic { - return &QueryDocumentDetailLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *QueryDocumentDetailLogic) QueryDocumentDetail(req *types.QueryDocumentDetailRequest) (resp *types.Document, err error) { - // find document - data, err := l.svcCtx.DocumentModel.FindOne(l.ctx, req.Id) - if err != nil { - l.Error("[QueryDocumentDetailLogic] FindOne error", logger.Field("id", req.Id), logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindOne error: %s", err.Error()) - } - resp = &types.Document{} - tool.DeepCopy(resp, data) - return -} diff --git a/internal/logic/app/document/queryDocumentListLogic.go b/internal/logic/app/document/queryDocumentListLogic.go deleted file mode 100644 index 447e7ed..0000000 --- a/internal/logic/app/document/queryDocumentListLogic.go +++ /dev/null @@ -1,48 +0,0 @@ -package document - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type QueryDocumentListLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get document list -func NewQueryDocumentListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryDocumentListLogic { - return &QueryDocumentListLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *QueryDocumentListLogic) QueryDocumentList() (resp *types.QueryDocumentListResponse, err error) { - total, data, err := l.svcCtx.DocumentModel.GetDocumentListByAll(l.ctx) - if err != nil { - l.Error("[QueryDocumentList] error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "QueryDocumentList error: %v", err.Error()) - } - resp = &types.QueryDocumentListResponse{ - Total: total, - List: make([]types.Document, 0), - } - for _, item := range data { - resp.List = append(resp.List, types.Document{ - Id: item.Id, - Title: item.Title, - Tags: tool.StringMergeAndRemoveDuplicates(item.Tags), - UpdatedAt: item.UpdatedAt.UnixMilli(), - }) - } - return -} diff --git a/internal/logic/app/node/getNodeListLogic.go b/internal/logic/app/node/getNodeListLogic.go deleted file mode 100644 index d37b4ed..0000000 --- a/internal/logic/app/node/getNodeListLogic.go +++ /dev/null @@ -1,91 +0,0 @@ -package node - -import ( - "context" - "strconv" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/countryCenter" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -type GetNodeListLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get Node list -func NewGetNodeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetNodeListLogic { - return &GetNodeListLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *GetNodeListLogic) GetNodeList(req *types.AppUserSubscbribeNodeRequest) (resp *types.AppUserSubscbribeNodeResponse, err error) { - resp = &types.AppUserSubscbribeNodeResponse{List: make([]types.AppUserSubscbribeNode, 0)} - userInfo := l.ctx.Value(constant.CtxKeyUser).(*user.User) - userSubscribe, err := l.svcCtx.UserModel.FindOneUserSubscribe(l.ctx, req.Id) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find user subscribe: %v", err.Error()) - } - - if userInfo.Id != userSubscribe.UserId { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "find user subscribe: %v", err.Error()) - } - - //拿到所有订阅下的服务组id - var ids []int64 - for _, idStr := range strings.Split(userSubscribe.Subscribe.ServerGroup, ",") { - id, err := strconv.ParseInt(idStr, 10, 64) - if err != nil { - continue - } - ids = append(ids, id) - } - - //根据服务组id拿到所有节点 - servers, err := l.svcCtx.ServerModel.FindServerListByGroupIds(l.ctx, ids) - if err != nil { - return nil, err - } - for _, server := range servers { - latitude, longitude, found := countryCenter.GetCountryCenterByCountryOrCity(server.Country, server.City) - if !found { - latitude = server.Latitude - longitude = server.Longitude - } - - resp.List = append(resp.List, types.AppUserSubscbribeNode{ - Id: server.Id, - Uuid: userSubscribe.UUID, - Traffic: userSubscribe.Traffic, - Upload: userSubscribe.Upload, - Download: userSubscribe.Download, - RelayNode: server.RelayNode, - RelayMode: server.RelayMode, - Longitude: server.Longitude, - Latitude: server.Latitude, - LatitudeCountry: latitude, - LongitudeCountry: longitude, - Tags: strings.Split(server.Tags, ","), - Config: server.Config, - ServerAddr: server.ServerAddr, - Protocol: server.Protocol, - SpeedLimit: server.SpeedLimit, - City: server.City, - Country: server.Country, - Name: server.Name, - }) - } - return -} diff --git a/internal/logic/app/node/getRuleGroupListLogic.go b/internal/logic/app/node/getRuleGroupListLogic.go deleted file mode 100644 index 70350cd..0000000 --- a/internal/logic/app/node/getRuleGroupListLogic.go +++ /dev/null @@ -1,41 +0,0 @@ -package node - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type GetRuleGroupListLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get rule group list -func NewGetRuleGroupListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetRuleGroupListLogic { - return &GetRuleGroupListLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *GetRuleGroupListLogic) GetRuleGroupList() (resp *types.AppRuleGroupListResponse, err error) { - nodeRuleGroupList, err := l.svcCtx.ServerModel.QueryAllRuleGroup(l.ctx) - if err != nil { - l.Logger.Error("[GetRuleGroupList] get subscribe rule group list failed: ", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get subscribe rule group list failed: %v", err.Error()) - } - nodeRuleGroups := make([]types.ServerRuleGroup, 0) - tool.DeepCopy(&nodeRuleGroups, nodeRuleGroupList) - return &types.AppRuleGroupListResponse{ - Total: int64(len(nodeRuleGroups)), - List: nodeRuleGroups, - }, nil -} diff --git a/internal/logic/app/order/calculateCoupon.go b/internal/logic/app/order/calculateCoupon.go deleted file mode 100644 index e9150cb..0000000 --- a/internal/logic/app/order/calculateCoupon.go +++ /dev/null @@ -1,13 +0,0 @@ -package order - -import ( - "github.com/perfect-panel/ppanel-server/internal/model/coupon" -) - -func calculateCoupon(amount int64, couponInfo *coupon.Coupon) int64 { - if couponInfo.Type == 1 { - return int64(float64(amount) * (float64(couponInfo.Discount) / float64(100))) - } else { - return min(couponInfo.Discount, amount) - } -} diff --git a/internal/logic/app/order/calculateFee.go b/internal/logic/app/order/calculateFee.go deleted file mode 100644 index 432ad27..0000000 --- a/internal/logic/app/order/calculateFee.go +++ /dev/null @@ -1,20 +0,0 @@ -package order - -import "github.com/perfect-panel/ppanel-server/internal/model/payment" - -func calculateFee(amount int64, config *payment.Payment) int64 { - var fee float64 - switch config.FeeMode { - case 0: - return 0 - case 1: - fee = float64(amount) * (float64(config.FeePercent) / float64(100)) - case 2: - if amount > 0 { - fee = float64(config.FeeAmount) - } - case 3: - fee = float64(amount)*(float64(config.FeePercent)/float64(100)) + float64(config.FeeAmount) - } - return int64(fee) -} diff --git a/internal/logic/app/order/checkoutOrderLogic.go b/internal/logic/app/order/checkoutOrderLogic.go deleted file mode 100644 index 1852943..0000000 --- a/internal/logic/app/order/checkoutOrderLogic.go +++ /dev/null @@ -1,373 +0,0 @@ -package order - -import ( - "context" - "encoding/json" - "fmt" - "strconv" - - order2 "github.com/perfect-panel/ppanel-server/internal/logic/public/order" - "github.com/perfect-panel/ppanel-server/pkg/payment/payssion" - - paymentPlatform "github.com/perfect-panel/ppanel-server/pkg/payment" - - "github.com/perfect-panel/ppanel-server/pkg/constant" - - "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/model/payment" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/exchangeRate" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/payment/alipay" - "github.com/perfect-panel/ppanel-server/pkg/payment/epay" - "github.com/perfect-panel/ppanel-server/pkg/payment/stripe" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - queueType "github.com/perfect-panel/ppanel-server/queue/types" - "github.com/pkg/errors" - "gorm.io/gorm" -) - -type CheckoutOrderLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -type CurrencyConfig struct { - CurrencyUnit string - CurrencySymbol string - AccessKey string -} - -const ( - Stripe = "Stripe" - QR = "qr" - Link = "link" -) - -// NewCheckoutOrderLogic Checkout order -func NewCheckoutOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CheckoutOrderLogic { - return &CheckoutOrderLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *CheckoutOrderLogic) CheckoutOrder(req *types.CheckoutOrderRequest, requestHost string) (resp *types.CheckoutOrderResponse, err error) { - u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User) - if !ok { - l.Error("[CheckoutOrderLogic] Invalid access") - return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid access") - } - // find order - orderInfo, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo) - if err != nil { - l.Error("[CheckoutOrderLogic] FindOneByOrderNo error", logger.Field("orderNo", req.OrderNo), logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindOneByOrderNo error: %s", err.Error()) - } - - if orderInfo.Status != 1 { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Order status error") - } - - paymentConfig, err := l.svcCtx.PaymentModel.FindOne(l.ctx, orderInfo.PaymentId) - if err != nil { - l.Error("[CheckoutOrderLogic] FindOneByPaymentMark error", logger.Field("paymentMark", orderInfo.Method), logger.Field("PaymentID", orderInfo.PaymentId), logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindOneByPaymentMark error: %s", err.Error()) - } - var stripePayment *types.StripePayment = nil - var url, t string - - // switch payment method - switch paymentPlatform.ParsePlatform(paymentConfig.Platform) { - case paymentPlatform.Stripe: - result, err := l.stripePayment(paymentConfig.Config, orderInfo, u) - if err != nil { - l.Error("[CheckoutOrderLogic] stripePayment error", logger.Field("error", err.Error())) - return nil, err - } - stripePayment = result - t = Stripe - case paymentPlatform.EPay: - // epay - url, err = l.epayPayment(paymentConfig, orderInfo, req.ReturnUrl, requestHost) - if err != nil { - l.Error("[CheckoutOrderLogic] epayPayment error", logger.Field("error", err.Error())) - return nil, err - } - t = Link - case paymentPlatform.AlipayF2F: - // alipay f2f - url, err = l.alipayF2fPayment(paymentConfig, orderInfo, requestHost) - if err != nil { - return nil, err - } - t = QR - case paymentPlatform.Payssion: - logger.Infof("匹配配置类型: %v", order2.Payssion) - url, err = l.payssionPayment(paymentConfig, orderInfo, requestHost) - if err != nil { - l.Error("[CheckoutOrderLogic] epayPayment error", logger.Field("error", err.Error())) - return nil, err - } - t = Link - case paymentPlatform.Balance: - // balance - if err = l.balancePayment(u, orderInfo); err != nil { - return nil, err - } - t = paymentPlatform.Balance.String() - default: - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Payment method not supported") - } - return &types.CheckoutOrderResponse{ - Type: t, - CheckoutUrl: url, - Stripe: stripePayment, - }, nil -} - -// Query exchange rate -func (l *CheckoutOrderLogic) queryExchangeRate(to string, src int64) (amount float64, err error) { - amount = float64(src) / float64(100) - // query system currency - currency, err := l.svcCtx.SystemModel.GetCurrencyConfig(l.ctx) - if err != nil { - l.Error("[CheckoutOrderLogic] GetCurrencyConfig error", logger.Field("error", err.Error())) - return 0, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetCurrencyConfig error: %s", err.Error()) - } - configs := &CurrencyConfig{} - tool.SystemConfigSliceReflectToStruct(currency, configs) - if configs.AccessKey == "" { - return amount, nil - } - if configs.CurrencyUnit != to { - // query exchange rate - result, err := exchangeRate.GetExchangeRete(configs.CurrencyUnit, to, configs.AccessKey, 1) - if err != nil { - return 0, err - } - amount = result * amount - } - return amount, nil -} - -// Stripe Payment -func (l *CheckoutOrderLogic) stripePayment(config string, info *order.Order, u *user.User) (*types.StripePayment, error) { - // stripe WeChat pay or stripe alipay - stripeConfig := payment.StripeConfig{} - if err := json.Unmarshal([]byte(config), &stripeConfig); err != nil { - l.Error("[CheckoutOrderLogic] Unmarshal error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error()) - } - client := stripe.NewClient(stripe.Config{ - SecretKey: stripeConfig.SecretKey, - PublicKey: stripeConfig.PublicKey, - WebhookSecret: stripeConfig.WebhookSecret, - }) - // Calculate the amount with exchange rate - amount, err := l.queryExchangeRate("CNY", info.Amount) - if err != nil { - l.Error("[CheckoutOrderLogic] queryExchangeRate error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "queryExchangeRate error: %s", err.Error()) - } - convertAmount := int64(amount * 100) - // create payment - result, err := client.CreatePaymentSheet(&stripe.Order{ - OrderNo: info.OrderNo, - Subscribe: strconv.FormatInt(info.SubscribeId, 10), - Amount: convertAmount, - Currency: "cny", - Payment: stripeConfig.Payment, - }, - &stripe.User{ - UserId: u.Id, - }) - if err != nil { - l.Error("[CheckoutOrderLogic] CreatePaymentSheet error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "CreatePaymentSheet error: %s", err.Error()) - } - tradeNo := result.TradeNo - stripePayment := &types.StripePayment{ - PublishableKey: stripeConfig.PublicKey, - ClientSecret: result.ClientSecret, - Method: stripeConfig.Payment, - } - // save payment - info.TradeNo = tradeNo - err = l.svcCtx.OrderModel.Update(l.ctx, info) - if err != nil { - l.Error("[CheckoutOrderLogic] Update error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Update error: %s", err.Error()) - } - return stripePayment, nil -} - -// epay payment -func (l *CheckoutOrderLogic) epayPayment(config *payment.Payment, info *order.Order, returnUrl, requestHost string) (string, error) { - epayConfig := payment.EPayConfig{} - if err := json.Unmarshal([]byte(config.Config), &epayConfig); err != nil { - l.Errorw("[CheckoutOrderLogic] Unmarshal error", logger.Field("error", err.Error())) - return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error()) - } - client := epay.NewClient(epayConfig.Pid, epayConfig.Url, epayConfig.Key) - // Calculate the amount with exchange rate - amount, err := l.queryExchangeRate("CNY", info.Amount) - if err != nil { - return "", err - } - notifyUrl := "" - if config.Domain != "" { - notifyUrl = config.Domain + "/v1/notify/" + config.Platform + "/" + config.Token - } else { - host, ok := l.ctx.Value(constant.CtxKeyRequestHost).(string) - if !ok { - host = l.svcCtx.Config.Host - } - notifyUrl = "https://" + host + "/v1/notify/" + config.Platform + "/" + config.Token - } - // create payment - url := client.CreatePayUrl(epay.Order{ - Name: l.svcCtx.Config.Site.SiteName, - Amount: amount, - OrderNo: info.OrderNo, - SignType: "MD5", - NotifyUrl: notifyUrl, - ReturnUrl: returnUrl, - }) - return url, nil -} - -// alipay f2f payment -func (l *CheckoutOrderLogic) alipayF2fPayment(pay *payment.Payment, info *order.Order, requestHost string) (string, error) { - f2FConfig := payment.AlipayF2FConfig{} - if err := json.Unmarshal([]byte(pay.Config), &f2FConfig); err != nil { - l.Error("[CheckoutOrderLogic] Unmarshal error", logger.Field("error", err.Error())) - return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error()) - } - var domain string - if pay.Domain != "" { - domain = pay.Domain - } else { - domain = fmt.Sprintf("http://%s", requestHost) - } - client := alipay.NewClient(alipay.Config{ - AppId: f2FConfig.AppId, - PrivateKey: f2FConfig.PrivateKey, - PublicKey: f2FConfig.PublicKey, - InvoiceName: f2FConfig.InvoiceName, - NotifyURL: domain + "/notify/alipay", - }) - // Calculate the amount with exchange rate - amount, err := l.queryExchangeRate("CNY", info.Amount) - if err != nil { - l.Error("[CheckoutOrderLogic] queryExchangeRate error", logger.Field("error", err.Error())) - return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "queryExchangeRate error: %s", err.Error()) - } - convertAmount := int64(amount * 100) - // create payment - QRCode, err := client.PreCreateTrade(l.ctx, alipay.Order{ - OrderNo: info.OrderNo, - Amount: convertAmount, - }) - if err != nil { - l.Error("[CheckoutOrderLogic] PreCreateTrade error", logger.Field("error", err.Error())) - return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "PreCreateTrade error: %s", err.Error()) - } - return QRCode, nil -} - -// Balance payment -func (l *CheckoutOrderLogic) balancePayment(u *user.User, o *order.Order) error { - var userInfo user.User - err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { - err := db.Model(&user.User{}).Where("id = ?", u.Id).First(&userInfo).Error - if err != nil { - return err - } - - if userInfo.Balance < o.Amount { - return errors.Wrapf(xerr.NewErrCode(xerr.InsufficientBalance), "Insufficient balance") - } - // deduct balance - userInfo.Balance -= o.Amount - err = l.svcCtx.UserModel.Update(l.ctx, &userInfo) - if err != nil { - return err - } - // create balance log - balanceLog := &user.BalanceLog{ - Id: 0, - UserId: u.Id, - Amount: o.Amount, - Type: 3, - OrderId: o.Id, - Balance: userInfo.Balance, - } - err = db.Create(balanceLog).Error - if err != nil { - return err - } - return l.svcCtx.OrderModel.UpdateOrderStatus(l.ctx, o.OrderNo, 2) - }) - if err != nil { - l.Error("[CheckoutOrderLogic] Transaction error", logger.Field("error", err.Error()), logger.Field("orderNo", o.OrderNo)) - return err - } - // create activity order task - payload := queueType.ForthwithActivateOrderPayload{ - OrderNo: o.OrderNo, - } - bytes, err := json.Marshal(payload) - if err != nil { - l.Error("[CheckoutOrderLogic] Marshal error", logger.Field("error", err.Error())) - return err - } - - task := asynq.NewTask(queueType.ForthwithActivateOrder, bytes) - _, err = l.svcCtx.Queue.EnqueueContext(l.ctx, task) - if err != nil { - l.Error("[CheckoutOrderLogic] Enqueue error", logger.Field("error", err.Error())) - return err - } - l.Logger.Info("[CheckoutOrderLogic] Enqueue success", logger.Field("orderNo", o.OrderNo)) - return nil -} - -func (l *CheckoutOrderLogic) payssionPayment(config *payment.Payment, info *order.Order, returnUrl string) (string, error) { - payssionConfig := payment.PayssionConfig{} - if err := json.Unmarshal([]byte(config.Config), &payssionConfig); err != nil { - l.Errorw("[CheckoutOrderLogic] Unmarshal error", logger.Field("error", err.Error())) - return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error()) - } - client := payssion.NewClient(payssionConfig.ApiKey, payssionConfig.SecretKey, payssionConfig.PmId, payssionConfig.Currency, payssionConfig.QueryUrl, payssionConfig.CreateUrl) - // Calculate the amount with exchange rate - amount, err := l.queryExchangeRate("CNY", info.Amount) - if err != nil { - return "", err - } - notifyUrl := "" - if config.Domain != "" { - notifyUrl = config.Domain + "/v1/notify/" + config.Platform + "/" + config.Token - } else { - host, ok := l.ctx.Value(constant.CtxKeyRequestHost).(string) - if !ok { - host = l.svcCtx.Config.Host - } - notifyUrl = "https://" + host + "/v1/notify/" + config.Platform + "/" + config.Token - } - // create payment - url, err := client.CreateOrder(payssion.Order{ - Name: l.svcCtx.Config.Site.SiteName, - Amount: amount, - OrderNo: info.OrderNo, - NotifyUrl: notifyUrl, - ReturnUrl: returnUrl, - }) - return url, err -} diff --git a/internal/logic/app/order/closeOrderLogic.go b/internal/logic/app/order/closeOrderLogic.go deleted file mode 100644 index b98abc9..0000000 --- a/internal/logic/app/order/closeOrderLogic.go +++ /dev/null @@ -1,205 +0,0 @@ -package order - -import ( - "context" - "encoding/json" - "github.com/perfect-panel/ppanel-server/pkg/payment/payssion" - - paymentPlatform "github.com/perfect-panel/ppanel-server/pkg/payment" - - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/payment/stripe" - "gorm.io/gorm" - - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/model/payment" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/payment/alipay" -) - -type CloseOrderLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// NewCloseOrderLogic Close order -func NewCloseOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CloseOrderLogic { - return &CloseOrderLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *CloseOrderLogic) CloseOrder(req *types.CloseOrderRequest) error { - // Find order information by order number - orderInfo, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo) - if err != nil { - l.Error("[CloseOrder] Find order info failed", - logger.Field("error", err.Error()), - logger.Field("orderNo", req.OrderNo), - ) - return nil - } - // If the order status is not 1, it means that the order has been closed or paid - if orderInfo.Status != 1 { - l.Info("[CloseOrder] Order status is not 1", - logger.Field("orderNo", req.OrderNo), - logger.Field("status", orderInfo.Status), - ) - return nil - } - - err = l.svcCtx.DB.Transaction(func(tx *gorm.DB) error { - // update order status - err := tx.Model(&order.Order{}).Where("order_no = ?", req.OrderNo).Update("status", 3).Error - if err != nil { - l.Error("[CloseOrder] Update order status failed", - logger.Field("error", err.Error()), - logger.Field("orderNo", req.OrderNo), - ) - return err - } - // refund deduction amount to user deduction balance - if orderInfo.GiftAmount > 0 { - userInfo, err := l.svcCtx.UserModel.FindOne(l.ctx, orderInfo.UserId) - if err != nil { - l.Error("[CloseOrder] Find user info failed", - logger.Field("error", err.Error()), - logger.Field("user_id", orderInfo.UserId), - ) - return err - } - deduction := userInfo.GiftAmount + orderInfo.GiftAmount - err = tx.Model(&user.User{}).Where("id = ?", orderInfo.UserId).Update("deduction", deduction).Error - if err != nil { - l.Error("[CloseOrder] Refund deduction amount failed", - logger.Field("error", err.Error()), - logger.Field("uid", orderInfo.UserId), - logger.Field("deduction", orderInfo.GiftAmount), - ) - return err - } - // Record the deduction refund log - giftAmountLog := &user.GiftAmountLog{ - UserId: orderInfo.UserId, - OrderNo: orderInfo.OrderNo, - Amount: orderInfo.GiftAmount, - Type: 1, - Balance: deduction, - Remark: "Order cancellation refund", - } - err = tx.Model(&user.GiftAmountLog{}).Create(giftAmountLog).Error - if err != nil { - l.Error("[CloseOrder] Record cancellation refund log failed", - logger.Field("error", err.Error()), - logger.Field("uid", orderInfo.UserId), - logger.Field("deduction", orderInfo.GiftAmount), - ) - return err - } - // update user cache - return l.svcCtx.UserModel.UpdateUserCache(l.ctx, userInfo) - } - return nil - }) - if err != nil { - return err - } - return nil -} - -// confirmationPayment Determine whether the payment is successful -// -//nolint:unused -func (l *CloseOrderLogic) confirmationPayment(order *order.Order) bool { - paymentConfig, err := l.svcCtx.PaymentModel.FindOne(l.ctx, order.PaymentId) - if err != nil { - l.Error("[CloseOrder] Find payment config failed", logger.Field("error", err.Error()), logger.Field("paymentMark", order.Method)) - return false - } - switch paymentPlatform.ParsePlatform(order.Method) { - case paymentPlatform.AlipayF2F: - if l.queryAlipay(paymentConfig, order.TradeNo) { - return true - } - case paymentPlatform.Payssion: - if l.queryPayssion(paymentConfig, order.TradeNo) { - return true - } - case paymentPlatform.Stripe: - if l.queryStripe(paymentConfig, order.TradeNo) { - return true - } - default: - l.Info("[CloseOrder] Unsupported payment method", logger.Field("paymentMethod", order.Method)) - } - return false -} - -// queryAlipay Query Alipay payment status -func (l *CloseOrderLogic) queryAlipay(paymentConfig *payment.Payment, TradeNo string) bool { - config := payment.AlipayF2FConfig{} - if err := json.Unmarshal([]byte(paymentConfig.Config), &config); err != nil { - l.Error("[CloseOrder] Unmarshal payment config failed", logger.Field("error", err.Error()), logger.Field("config", paymentConfig.Config)) - return false - } - client := alipay.NewClient(alipay.Config{ - AppId: config.AppId, - PrivateKey: config.PrivateKey, - PublicKey: config.PublicKey, - InvoiceName: config.InvoiceName, - }) - status, err := client.QueryTrade(l.ctx, TradeNo) - if err != nil { - l.Error("[CloseOrder] Query trade failed", logger.Field("error", err.Error()), logger.Field("TradeNo", TradeNo)) - return false - } - if status == alipay.Success || status == alipay.Finished { - return true - } - return false -} - -// queryStripe Query Stripe payment status -func (l *CloseOrderLogic) queryStripe(paymentConfig *payment.Payment, TradeNo string) bool { - config := payment.StripeConfig{} - if err := json.Unmarshal([]byte(paymentConfig.Config), &config); err != nil { - l.Error("[CloseOrder] Unmarshal payment config failed", logger.Field("error", err.Error()), logger.Field("config", paymentConfig.Config)) - return false - } - client := stripe.NewClient(stripe.Config{ - PublicKey: config.PublicKey, - SecretKey: config.SecretKey, - WebhookSecret: config.WebhookSecret, - }) - status, err := client.QueryOrderStatus(TradeNo) - if err != nil { - l.Error("[CloseOrder] Query order status failed", logger.Field("error", err.Error()), logger.Field("TradeNo", TradeNo)) - return false - } - return status -} - -//nolint:unused -func (l *CloseOrderLogic) queryPayssion(paymentConfig *payment.Payment, TradeNo string) bool { - l.Info("[CloseOrder]1 Query Payssion", logger.Field("TradeNo", TradeNo)) - payssionConfig := payment.PayssionConfig{} - if err := json.Unmarshal([]byte(paymentConfig.Config), &payssionConfig); err != nil { - l.Errorw("[CloseOrder] Unmarshal error", logger.Field("error", err.Error())) - return false - } - client := payssion.NewClient(payssionConfig.ApiKey, payssionConfig.SecretKey, payssionConfig.PmId, payssionConfig.Currency, payssionConfig.QueryUrl, payssionConfig.CreateUrl) - l.Infof("[CloseOrder]2 Query Payssion", logger.Field("TradeNo", TradeNo)) - // create payment - result, err := client.QueryOrder(TradeNo) - if err != nil { - l.Errorw("[CloseOrder] Query order status failed", logger.Field("error", err.Error()), logger.Field("TradeNo", TradeNo)) - return false - } - l.Infof("[CloseOrder]3 Query Payssion", logger.Field("TradeNo", TradeNo)) - return result.Transaction.State == "completed" -} diff --git a/internal/logic/app/order/getDiscount.go b/internal/logic/app/order/getDiscount.go deleted file mode 100644 index 4d896f9..0000000 --- a/internal/logic/app/order/getDiscount.go +++ /dev/null @@ -1,14 +0,0 @@ -package order - -import "github.com/perfect-panel/ppanel-server/internal/types" - -func getDiscount(discounts []types.SubscribeDiscount, inputMonths int64) float64 { - var finalDiscount int64 = 100 - - for _, discount := range discounts { - if inputMonths >= discount.Quantity && discount.Discount < finalDiscount { - finalDiscount = discount.Discount - } - } - return float64(finalDiscount) / float64(100) -} diff --git a/internal/logic/app/order/preCreateOrderLogic.go b/internal/logic/app/order/preCreateOrderLogic.go deleted file mode 100644 index d36cd10..0000000 --- a/internal/logic/app/order/preCreateOrderLogic.go +++ /dev/null @@ -1,104 +0,0 @@ -package order - -import ( - "context" - "encoding/json" - - "github.com/perfect-panel/ppanel-server/pkg/constant" - - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" -) - -type PreCreateOrderLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Pre create order -func NewPreCreateOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PreCreateOrderLogic { - return &PreCreateOrderLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *PreCreateOrderLogic) PreCreateOrder(req *types.PurchaseOrderRequest) (resp *types.PreOrderResponse, 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") - } - // find subscribe plan - sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, req.SubscribeId) - if err != nil { - l.Error("[PreCreateOrder] Database query error", logger.Field("error", err.Error()), logger.Field("subscribe_id", req.SubscribeId)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe error: %v", err.Error()) - } - var discount float64 = 1 - if sub.Discount != "" { - var dis []types.SubscribeDiscount - _ = json.Unmarshal([]byte(sub.Discount), &dis) - discount = getDiscount(dis, req.Quantity) - } - price := sub.UnitPrice * req.Quantity - amount := int64(float64(price) * discount) - discountAmount := price - amount - var coupon int64 - if req.Coupon != "" { - couponInfo, err := l.svcCtx.CouponModel.FindOneByCode(l.ctx, req.Coupon) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponNotExist), "coupon not found") - } - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find coupon error: %v", err.Error()) - } - if couponInfo.Count <= couponInfo.UsedCount { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponUsed), "coupon used") - } - coupon = calculateCoupon(amount, couponInfo) - } - amount -= coupon - - var deductionAmount int64 - // Check user deduction amount - if u.GiftAmount > 0 { - if u.GiftAmount >= amount { - deductionAmount = amount - amount = 0 - } else { - deductionAmount = u.GiftAmount - amount -= u.GiftAmount - } - } - - payment, err := l.svcCtx.PaymentModel.FindOne(l.ctx, req.Payment) - if err != nil { - l.Logger.Error("[PreCreateOrder] Database query error", logger.Field("error", err.Error()), logger.Field("payment", req.Payment)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find payment method error: %v", err.Error()) - } - var feeAmount int64 - // Calculate the handling fee - if amount > 0 { - feeAmount = calculateFee(amount, payment) - } - amount += feeAmount - - resp = &types.PreOrderResponse{ - Price: price, - Amount: amount, - Discount: discountAmount, - GiftAmount: deductionAmount, - Coupon: req.Coupon, - CouponDiscount: coupon, - FeeAmount: feeAmount, - } - return -} diff --git a/internal/logic/app/order/purchaseLogic.go b/internal/logic/app/order/purchaseLogic.go deleted file mode 100644 index 7934cc6..0000000 --- a/internal/logic/app/order/purchaseLogic.go +++ /dev/null @@ -1,204 +0,0 @@ -package order - -import ( - "context" - "encoding/json" - "time" - - "github.com/perfect-panel/ppanel-server/pkg/constant" - - "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - queue "github.com/perfect-panel/ppanel-server/queue/types" - "github.com/pkg/errors" - "gorm.io/gorm" -) - -type PurchaseLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -const CloseOrderTimeMinutes = 15 - -// purchase Subscription -func NewPurchaseLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PurchaseLogic { - return &PurchaseLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.PurchaseOrderResponse, 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") - } - // find user subscription - - if l.svcCtx.Config.Subscribe.SingleModel { - userSubs, err := l.svcCtx.UserModel.QueryUserSubscribe(l.ctx, u.Id) - if err != nil { - l.Error("[Purchase] Database query error", logger.Field("error", err.Error()), logger.Field("user_id", u.Id)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find user subscription error: %v", err.Error()) - } - if (len(userSubs) == 1 && //订阅数等于1 - //启用试用 - l.svcCtx.Config.Register.EnableTrial && - //试用订阅ID不等于0 - l.svcCtx.Config.Register.TrialSubscribe != 0 && - //使用者订阅ID不等于试用订阅ID - userSubs[0].SubscribeId != l.svcCtx.Config.Register.TrialSubscribe) || len(userSubs) > 1 { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserSubscribeExist), "user has subscription") - } - } - - // find subscribe plan - sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, req.SubscribeId) - - if err != nil { - l.Error("[Purchase] Database query error", logger.Field("error", err.Error()), logger.Field("subscribe_id", req.SubscribeId)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe error: %v", err.Error()) - } - // check subscribe plan status - if !*sub.Sell { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "subscribe not sell") - } - var discount float64 = 1 - if sub.Discount != "" { - var dis []types.SubscribeDiscount - _ = json.Unmarshal([]byte(sub.Discount), &dis) - discount = getDiscount(dis, req.Quantity) - } - price := sub.UnitPrice * req.Quantity - // discount amount - amount := int64(float64(price) * discount) - discountAmount := price - amount - var coupon int64 = 0 - // Calculate the coupon deduction - if req.Coupon != "" { - couponInfo, err := l.svcCtx.CouponModel.FindOneByCode(l.ctx, req.Coupon) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponNotExist), "coupon not found") - } - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find coupon error: %v", err.Error()) - } - if couponInfo.Count <= couponInfo.UsedCount { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponUsed), "coupon used") - } - coupon = calculateCoupon(amount, couponInfo) - } - // Calculate the handling fee - amount -= coupon - var deductionAmount int64 - // Check user deduction amount - if u.GiftAmount > 0 { - if u.GiftAmount >= amount { - deductionAmount = amount - amount = 0 - u.GiftAmount -= amount - } else { - deductionAmount = u.GiftAmount - amount -= u.GiftAmount - u.GiftAmount = 0 - } - } - // find payment method - payment, err := l.svcCtx.PaymentModel.FindOne(l.ctx, req.Payment) - if err != nil { - l.Logger.Error("[Purchase] Database query error", logger.Field("error", err.Error()), logger.Field("payment", req.Payment)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find payment method error: %v", err.Error()) - } - var feeAmount int64 - // Calculate the handling fee - if amount > 0 { - feeAmount = calculateFee(amount, payment) - } - // query user is new purchase or renewal - isNew, err := l.svcCtx.OrderModel.IsUserEligibleForNewOrder(l.ctx, u.Id) - if err != nil { - l.Error("[Purchase] Database query error", logger.Field("error", err.Error()), logger.Field("user_id", u.Id)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find user order error: %v", err.Error()) - } - // create order - orderInfo := &order.Order{ - UserId: u.Id, - OrderNo: tool.GenerateTradeNo(), - Type: 1, - Quantity: req.Quantity, - Price: price, - Amount: amount, - Discount: discountAmount, - GiftAmount: deductionAmount, - Coupon: req.Coupon, - CouponDiscount: coupon, - PaymentId: req.Payment, - Method: payment.Platform, - FeeAmount: feeAmount, - Status: 1, - IsNew: isNew, - SubscribeId: req.SubscribeId, - } - // Database transaction - err = l.svcCtx.DB.Transaction(func(db *gorm.DB) error { - // update user deduction && Pre deduction ,Return after canceling the order - if orderInfo.GiftAmount > 0 { - // update user deduction && Pre deduction ,Return after canceling the order - if e := l.svcCtx.UserModel.Update(l.ctx, u, db); err != nil { - l.Error("[Purchase] Database update error", logger.Field("error", err.Error()), logger.Field("user", u)) - return e - } - // create deduction record - giftAmountLog := user.GiftAmountLog{ - UserId: orderInfo.UserId, - OrderNo: orderInfo.OrderNo, - Amount: orderInfo.GiftAmount, - Type: 2, - Balance: u.GiftAmount, - Remark: "Purchase order deduction", - } - if e := db.Model(&user.GiftAmountLog{}).Create(&giftAmountLog).Error; e != nil { - l.Error("[Purchase] Database insert error", - logger.Field("error", err.Error()), - logger.Field("deductionLog", giftAmountLog), - ) - return e - } - } - // insert order - return db.Model(&order.Order{}).Create(&orderInfo).Error - }) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert order error: %v", err.Error()) - } - // Deferred task - payload := queue.DeferCloseOrderPayload{ - OrderNo: orderInfo.OrderNo, - } - val, err := json.Marshal(payload) - if err != nil { - l.Error("[CreateOrder] Marshal payload error", logger.Field("error", err.Error()), logger.Field("payload", payload)) - } - task := asynq.NewTask(queue.DeferCloseOrder, val, asynq.MaxRetry(3)) - taskInfo, err := l.svcCtx.Queue.Enqueue(task, asynq.ProcessIn(CloseOrderTimeMinutes*time.Minute)) - if err != nil { - l.Error("[CreateOrder] Enqueue task error", logger.Field("error", err.Error()), logger.Field("task", task)) - } else { - l.Info("[CreateOrder] Enqueue task success", logger.Field("TaskID", taskInfo.ID)) - } - - return &types.PurchaseOrderResponse{ - OrderNo: orderInfo.OrderNo, - }, nil -} diff --git a/internal/logic/app/order/queryOrderDetailLogic.go b/internal/logic/app/order/queryOrderDetailLogic.go deleted file mode 100644 index 5cc6b55..0000000 --- a/internal/logic/app/order/queryOrderDetailLogic.go +++ /dev/null @@ -1,40 +0,0 @@ -package order - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type QueryOrderDetailLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get order -func NewQueryOrderDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryOrderDetailLogic { - return &QueryOrderDetailLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *QueryOrderDetailLogic) QueryOrderDetail(req *types.QueryOrderDetailRequest) (resp *types.OrderDetail, err error) { - orderInfo, err := l.svcCtx.OrderModel.FindOneDetailsByOrderNo(l.ctx, req.OrderNo) - if err != nil { - l.Error("[QueryOrderDetail] Database query error", logger.Field("error", err.Error()), logger.Field("order_no", req.OrderNo)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find order error: %v", err.Error()) - } - resp = &types.OrderDetail{} - tool.DeepCopy(resp, orderInfo) - // Prevent commission amount leakage - resp.Commission = 0 - return -} diff --git a/internal/logic/app/order/queryOrderListLogic.go b/internal/logic/app/order/queryOrderListLogic.go deleted file mode 100644 index 245e5d6..0000000 --- a/internal/logic/app/order/queryOrderListLogic.go +++ /dev/null @@ -1,56 +0,0 @@ -package order - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/pkg/constant" - - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type QueryOrderListLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get order list -func NewQueryOrderListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryOrderListLogic { - return &QueryOrderListLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *QueryOrderListLogic) QueryOrderList(req *types.QueryOrderListRequest) (resp *types.QueryOrderListResponse, 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") - } - total, data, err := l.svcCtx.OrderModel.QueryOrderListByPage(l.ctx, req.Page, req.Size, 0, u.Id, 0, "") - if err != nil { - l.Error("[QueryOrderListLogic] Query order list failed", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query order list failed") - } - resp = &types.QueryOrderListResponse{ - Total: total, - List: make([]types.OrderDetail, 0), - } - for _, item := range data { - var orderInfo types.OrderDetail - tool.DeepCopy(&orderInfo, item) - // Prevent commission amount leakage - orderInfo.Commission = 0 - resp.List = append(resp.List, orderInfo) - } - - return -} diff --git a/internal/logic/app/order/rechargeLogic.go b/internal/logic/app/order/rechargeLogic.go deleted file mode 100644 index 2957c54..0000000 --- a/internal/logic/app/order/rechargeLogic.go +++ /dev/null @@ -1,92 +0,0 @@ -package order - -import ( - "context" - "encoding/json" - "time" - - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - - "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - queue "github.com/perfect-panel/ppanel-server/queue/types" - "github.com/pkg/errors" -) - -type RechargeLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// NewRechargeLogic Recharge -func NewRechargeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RechargeLogic { - return &RechargeLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *RechargeLogic) Recharge(req *types.RechargeOrderRequest) (resp *types.RechargeOrderResponse, 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") - } - // find payment method - payment, err := l.svcCtx.PaymentModel.FindOne(l.ctx, req.Payment) - if err != nil { - l.Error("[Recharge] Database query error", logger.Field("error", err.Error()), logger.Field("payment", req.Payment)) - return nil, errors.Wrapf(err, "find payment error: %v", err.Error()) - } - // Calculate the handling fee - feeAmount := calculateFee(req.Amount, payment) - // query user is new purchase or renewal - isNew, err := l.svcCtx.OrderModel.IsUserEligibleForNewOrder(l.ctx, u.Id) - if err != nil { - l.Error("[Recharge] Database query error", logger.Field("error", err.Error()), logger.Field("user_id", u.Id)) - return nil, errors.Wrapf(err, "query user error: %v", err.Error()) - } - orderInfo := order.Order{ - UserId: u.Id, - OrderNo: tool.GenerateTradeNo(), - Type: 4, - Price: req.Amount, - Amount: req.Amount + feeAmount, - FeeAmount: feeAmount, - PaymentId: req.Payment, - Method: payment.Platform, - Status: 1, - IsNew: isNew, - } - err = l.svcCtx.OrderModel.Insert(l.ctx, &orderInfo) - if err != nil { - l.Error("[Recharge] Database insert error", logger.Field("error", err.Error()), logger.Field("order", orderInfo)) - return nil, errors.Wrapf(err, "insert order error: %v", err.Error()) - } - // Deferred task - payload := queue.DeferCloseOrderPayload{ - OrderNo: orderInfo.OrderNo, - } - val, err := json.Marshal(payload) - if err != nil { - l.Error("[Recharge] Marshal payload error", logger.Field("error", err.Error()), logger.Field("payload", payload)) - } - task := asynq.NewTask(queue.DeferCloseOrder, val, asynq.MaxRetry(3)) - taskInfo, err := l.svcCtx.Queue.Enqueue(task, asynq.ProcessIn(CloseOrderTimeMinutes*time.Minute)) - if err != nil { - l.Error("[Recharge] Enqueue task error", logger.Field("error", err.Error()), logger.Field("task", task)) - } else { - l.Info("[Recharge] Enqueue task success", logger.Field("TaskID", taskInfo.ID)) - } - return &types.RechargeOrderResponse{ - OrderNo: orderInfo.OrderNo, - }, nil -} diff --git a/internal/logic/app/order/renewalLogic.go b/internal/logic/app/order/renewalLogic.go deleted file mode 100644 index 4878160..0000000 --- a/internal/logic/app/order/renewalLogic.go +++ /dev/null @@ -1,178 +0,0 @@ -package order - -import ( - "context" - "encoding/json" - "time" - - "github.com/perfect-panel/ppanel-server/pkg/constant" - - "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - queue "github.com/perfect-panel/ppanel-server/queue/types" - "github.com/pkg/errors" - "gorm.io/gorm" -) - -type RenewalLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Renewal Subscription -func NewRenewalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RenewalLogic { - return &RenewalLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *RenewalLogic) Renewal(req *types.RenewalOrderRequest) (resp *types.RenewalOrderResponse, 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") - } - orderNo := tool.GenerateTradeNo() - // find user subscribe - userSubscribe, err := l.svcCtx.UserModel.FindOneUserSubscribe(l.ctx, req.UserSubscribeID) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find user subscribe error: %v", err.Error()) - } - // find subscription - sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, userSubscribe.SubscribeId) - if err != nil { - l.Error("[Renewal] Database query error", logger.Field("error", err.Error()), logger.Field("subscribe_id", userSubscribe.SubscribeId)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe error: %v", err.Error()) - } - // check subscribe plan status - if !*sub.Sell { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "subscribe not sell") - } - var discount float64 = 1 - if sub.Discount != "" { - var dis []types.SubscribeDiscount - _ = json.Unmarshal([]byte(sub.Discount), &dis) - discount = getDiscount(dis, req.Quantity) - } - price := sub.UnitPrice * req.Quantity - amount := int64(float64(price) * discount) - discountAmount := price - amount - var coupon int64 = 0 - if req.Coupon != "" { - couponInfo, err := l.svcCtx.CouponModel.FindOneByCode(l.ctx, req.Coupon) - if err != nil { - l.Error("[Renewal] Database query error", logger.Field("error", err.Error()), logger.Field("coupon", req.Coupon)) - return nil, errors.Wrapf(err, "find coupon error: %v", err.Error()) - } - if couponInfo.Count <= couponInfo.UsedCount { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponUsed), "coupon used") - } - coupon = calculateCoupon(amount, couponInfo) - } - payment, err := l.svcCtx.PaymentModel.FindOne(l.ctx, req.Payment) - if err != nil { - l.Error("[Renewal] Database query error", logger.Field("error", err.Error()), logger.Field("payment", req.Payment)) - return nil, errors.Wrapf(err, "find payment error: %v", err.Error()) - } - amount -= coupon - - var deductionAmount int64 - // Check user deduction amount - if u.GiftAmount > 0 { - if u.GiftAmount >= amount { - deductionAmount = amount - amount = 0 - u.GiftAmount -= amount - } else { - deductionAmount = u.GiftAmount - amount -= u.GiftAmount - u.GiftAmount = 0 - } - } - - var feeAmount int64 - // Calculate the handling fee - if amount > 0 { - feeAmount = calculateFee(amount, payment) - } - - amount += feeAmount - - // create order - orderInfo := order.Order{ - UserId: u.Id, - ParentId: userSubscribe.OrderId, - OrderNo: orderNo, - Type: 2, - Quantity: req.Quantity, - Price: price, - Amount: amount, - GiftAmount: deductionAmount, - Discount: discountAmount, - Coupon: req.Coupon, - CouponDiscount: coupon, - PaymentId: payment.Id, - Method: payment.Platform, - FeeAmount: feeAmount, - Status: 1, - SubscribeId: userSubscribe.SubscribeId, - SubscribeToken: userSubscribe.Token, - } - // Database transaction - err = l.svcCtx.DB.Transaction(func(db *gorm.DB) error { - // update user deduction && Pre deduction ,Return after canceling the order - if orderInfo.GiftAmount > 0 { - // update user deduction && Pre deduction ,Return after canceling the order - if err := l.svcCtx.UserModel.Update(l.ctx, u, db); err != nil { - l.Error("[Purchase] Database update error", logger.Field("error", err.Error()), logger.Field("user", u)) - return err - } - // create deduction record - deductionLog := user.GiftAmountLog{ - UserId: orderInfo.UserId, - OrderNo: orderInfo.OrderNo, - Amount: orderInfo.GiftAmount, - Type: 2, - Balance: u.GiftAmount, - Remark: "Renewal order deduction", - } - if err := db.Model(&user.GiftAmountLog{}).Create(&deductionLog).Error; err != nil { - l.Error("[Renewal] Database insert error", logger.Field("error", err.Error()), logger.Field("deductionLog", deductionLog)) - return err - } - } - // insert order - return db.Model(&order.Order{}).Create(&orderInfo).Error - }) - if err != nil { - l.Error("[Renewal] Database insert error", logger.Field("error", err.Error()), logger.Field("order", orderInfo)) - return nil, errors.Wrapf(err, "insert order error: %v", err.Error()) - } - // Deferred task - payload := queue.DeferCloseOrderPayload{ - OrderNo: orderInfo.OrderNo, - } - val, err := json.Marshal(payload) - if err != nil { - l.Error("[Renewal] Marshal payload error", logger.Field("error", err.Error()), logger.Field("payload", payload)) - } - task := asynq.NewTask(queue.DeferCloseOrder, val, asynq.MaxRetry(3)) - taskInfo, err := l.svcCtx.Queue.Enqueue(task, asynq.ProcessIn(CloseOrderTimeMinutes*time.Minute)) - if err != nil { - l.Error("[Renewal] Enqueue task error", logger.Field("error", err.Error()), logger.Field("task", task)) - } else { - l.Info("[Renewal] Enqueue task success", logger.Field("TaskID", taskInfo.ID)) - } - return &types.RenewalOrderResponse{ - OrderNo: orderInfo.OrderNo, - }, nil -} diff --git a/internal/logic/app/order/resetTrafficLogic.go b/internal/logic/app/order/resetTrafficLogic.go deleted file mode 100644 index 1ec2f22..0000000 --- a/internal/logic/app/order/resetTrafficLogic.go +++ /dev/null @@ -1,146 +0,0 @@ -package order - -import ( - "context" - "encoding/json" - "time" - - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - - "gorm.io/gorm" - - "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/tool" - queue "github.com/perfect-panel/ppanel-server/queue/types" - "github.com/pkg/errors" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -type ResetTrafficLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Reset traffic -func NewResetTrafficLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResetTrafficLogic { - return &ResetTrafficLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *ResetTrafficLogic) ResetTraffic(req *types.ResetTrafficOrderRequest) (resp *types.ResetTrafficOrderResponse, 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") - } - // find user subscription - userSubscribe, err := l.svcCtx.UserModel.FindOneUserSubscribe(l.ctx, req.UserSubscribeID) - if err != nil { - l.Error("[ResetTraffic] Database query error", logger.Field("error", err.Error()), logger.Field("UserSubscribeID", req.UserSubscribeID)) - return nil, errors.Wrapf(err, "find user subscribe error: %v", err.Error()) - } - if userSubscribe.Subscribe == nil { - l.Error("[ResetTraffic] subscribe not found", logger.Field("UserSubscribeID", req.UserSubscribeID)) - return nil, errors.New("subscribe not found") - } - amount := userSubscribe.Subscribe.Replacement - var deductionAmount int64 - // Check user deduction amount - if u.GiftAmount > 0 { - if u.GiftAmount >= amount { - deductionAmount = amount - amount = 0 - u.GiftAmount -= amount - } else { - deductionAmount = u.GiftAmount - amount -= u.GiftAmount - u.GiftAmount = 0 - } - } - // find payment method - payment, err := l.svcCtx.PaymentModel.FindOne(l.ctx, req.Payment) - if err != nil { - l.Error("[ResetTraffic] Database query error", logger.Field("error", err.Error()), logger.Field("payment", req.Payment)) - return nil, errors.Wrapf(err, "find payment error: %v", err.Error()) - } - var feeAmount int64 - // Calculate the handling fee - if amount > 0 { - feeAmount = calculateFee(amount, payment) - } - // create order - orderInfo := order.Order{ - Id: 0, - ParentId: userSubscribe.OrderId, - UserId: u.Id, - OrderNo: tool.GenerateTradeNo(), - Type: 3, - Price: userSubscribe.Subscribe.Replacement, - Amount: amount + feeAmount, - GiftAmount: deductionAmount, - FeeAmount: feeAmount, - PaymentId: req.Payment, - Method: payment.Platform, - Status: 1, - SubscribeId: userSubscribe.SubscribeId, - SubscribeToken: userSubscribe.Token, - } - // Database transaction - err = l.svcCtx.DB.Transaction(func(db *gorm.DB) error { - // update user deduction && Pre deduction ,Return after canceling the order - if orderInfo.GiftAmount > 0 { - // update user deduction && Pre deduction ,Return after canceling the order - if err := l.svcCtx.UserModel.Update(l.ctx, u, db); err != nil { - l.Error("[ResetTraffic] Database update error", logger.Field("error", err.Error()), logger.Field("user", u)) - return err - } - // create deduction record - deductionLog := user.GiftAmountLog{ - UserId: orderInfo.UserId, - OrderNo: orderInfo.OrderNo, - Amount: orderInfo.GiftAmount, - Type: 2, - Balance: u.GiftAmount, - Remark: "ResetTraffic order deduction", - } - if err := db.Model(&user.GiftAmountLog{}).Create(&deductionLog).Error; err != nil { - l.Error("[ResetTraffic] Database insert error", logger.Field("error", err.Error()), logger.Field("deductionLog", deductionLog)) - return err - } - } - // insert order - return db.Model(&order.Order{}).Create(&orderInfo).Error - }) - if err != nil { - l.Error("[ResetTraffic] Database insert error", logger.Field("error", err.Error()), logger.Field("order", orderInfo)) - return nil, errors.Wrapf(err, "insert order error: %v", err.Error()) - } - // Deferred task - payload := queue.DeferCloseOrderPayload{ - OrderNo: orderInfo.OrderNo, - } - val, err := json.Marshal(payload) - if err != nil { - l.Error("[ResetTraffic] Marshal payload error", logger.Field("error", err.Error()), logger.Field("payload", payload)) - } - task := asynq.NewTask(queue.DeferCloseOrder, val, asynq.MaxRetry(3)) - taskInfo, err := l.svcCtx.Queue.Enqueue(task, asynq.ProcessIn(CloseOrderTimeMinutes*time.Minute)) - if err != nil { - l.Error("[ResetTraffic] Enqueue task error", logger.Field("error", err.Error()), logger.Field("task", task)) - } else { - l.Info("[ResetTraffic] Enqueue task success", logger.Field("TaskID", taskInfo.ID)) - } - return &types.ResetTrafficOrderResponse{ - OrderNo: orderInfo.OrderNo, - }, nil -} diff --git a/internal/logic/app/payment/getAvailablePaymentMethodsLogic.go b/internal/logic/app/payment/getAvailablePaymentMethodsLogic.go deleted file mode 100644 index 8d8a6d5..0000000 --- a/internal/logic/app/payment/getAvailablePaymentMethodsLogic.go +++ /dev/null @@ -1,40 +0,0 @@ -package payment - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type GetAvailablePaymentMethodsLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// NewGetAvailablePaymentMethodsLogic Get available payment methods -func NewGetAvailablePaymentMethodsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAvailablePaymentMethodsLogic { - return &GetAvailablePaymentMethodsLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *GetAvailablePaymentMethodsLogic) GetAvailablePaymentMethods() (resp *types.GetAvailablePaymentMethodsResponse, err error) { - data, err := l.svcCtx.PaymentModel.FindAvailableMethods(l.ctx) - if err != nil { - l.Error("[GetAvailablePaymentMethods] database error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetAvailablePaymentMethods: %v", err.Error()) - } - resp = &types.GetAvailablePaymentMethodsResponse{ - List: make([]types.PaymentMethod, 0), - } - tool.DeepCopy(&resp.List, data) - return -} diff --git a/internal/logic/app/subscribe/queryApplicationConfigLogic.go b/internal/logic/app/subscribe/queryApplicationConfigLogic.go deleted file mode 100644 index 4f091f4..0000000 --- a/internal/logic/app/subscribe/queryApplicationConfigLogic.go +++ /dev/null @@ -1,115 +0,0 @@ -package subscribe - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/model/application" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -type QueryApplicationConfigLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get application config -func NewQueryApplicationConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryApplicationConfigLogic { - return &QueryApplicationConfigLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *QueryApplicationConfigLogic) QueryApplicationConfig() (resp *types.ApplicationResponse, err error) { - resp = &types.ApplicationResponse{} - var applications []*application.Application - err = l.svcCtx.ApplicationModel.Transaction(l.ctx, func(tx *gorm.DB) (err error) { - return tx.Model(applications).Preload("ApplicationVersions").Find(&applications).Error - }) - if err != nil { - l.Errorw("[QueryApplicationConfig] get application error: ", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get application error: %v", err.Error()) - } - - if len(applications) == 0 { - return resp, nil - } - - for _, app := range applications { - applicationResponse := types.ApplicationResponseInfo{ - Id: app.Id, - Name: app.Name, - Icon: app.Icon, - Description: app.Description, - SubscribeType: app.SubscribeType, - } - applicationVersions := app.ApplicationVersions - if len(applicationVersions) != 0 { - for _, applicationVersion := range applicationVersions { - /*if !applicationVersion.IsDefault { - continue - }*/ - switch applicationVersion.Platform { - case "ios": - applicationResponse.Platform.IOS = append(applicationResponse.Platform.IOS, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "macos": - applicationResponse.Platform.MacOS = append(applicationResponse.Platform.MacOS, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "linux": - applicationResponse.Platform.Linux = append(applicationResponse.Platform.Linux, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "android": - applicationResponse.Platform.Android = append(applicationResponse.Platform.Android, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "windows": - applicationResponse.Platform.Windows = append(applicationResponse.Platform.Windows, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "harmony": - applicationResponse.Platform.Harmony = append(applicationResponse.Platform.Harmony, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - } - } - } - resp.Applications = append(resp.Applications, applicationResponse) - } - return -} diff --git a/internal/logic/app/subscribe/querySubscribeGroupListLogic.go b/internal/logic/app/subscribe/querySubscribeGroupListLogic.go deleted file mode 100644 index d0f08ac..0000000 --- a/internal/logic/app/subscribe/querySubscribeGroupListLogic.go +++ /dev/null @@ -1,44 +0,0 @@ -package subscribe - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/model/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type QuerySubscribeGroupListLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get subscribe group list -func NewQuerySubscribeGroupListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QuerySubscribeGroupListLogic { - return &QuerySubscribeGroupListLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *QuerySubscribeGroupListLogic) QuerySubscribeGroupList() (resp *types.QuerySubscribeGroupListResponse, err error) { - var list []*subscribe.Group - var total int64 - err = l.svcCtx.DB.Model(&subscribe.Group{}).Count(&total).Find(&list).Error - if err != nil { - l.Logger.Error("[QuerySubscribeGroupListLogic] get subscribe group list failed: ", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get subscribe group list failed: %v", err.Error()) - } - groupList := make([]types.SubscribeGroup, 0) - tool.DeepCopy(&groupList, list) - return &types.QuerySubscribeGroupListResponse{ - Total: total, - List: groupList, - }, nil -} diff --git a/internal/logic/app/subscribe/querySubscribeListLogic.go b/internal/logic/app/subscribe/querySubscribeListLogic.go deleted file mode 100644 index 22475d0..0000000 --- a/internal/logic/app/subscribe/querySubscribeListLogic.go +++ /dev/null @@ -1,55 +0,0 @@ -package subscribe - -import ( - "context" - "encoding/json" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type QuerySubscribeListLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get subscribe list -func NewQuerySubscribeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QuerySubscribeListLogic { - return &QuerySubscribeListLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *QuerySubscribeListLogic) QuerySubscribeList() (resp *types.QuerySubscribeListResponse, err error) { - - data, err := l.svcCtx.SubscribeModel.QuerySubscribeList(l.ctx) - if err != nil { - l.Errorw("[QuerySubscribeListLogic] Database Error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "QuerySubscribeList error: %v", err.Error()) - } - resp = &types.QuerySubscribeListResponse{ - List: make([]types.Subscribe, 0), - Total: int64(len(data)), - } - for _, v := range data { - var sub types.Subscribe - tool.DeepCopy(&sub, v) - if v.Discount != "" { - if err = json.Unmarshal([]byte(v.Discount), &sub.Discount); err != nil { - l.Errorw("[QuerySubscribeListLogic] json.Unmarshal Error", logger.Field("error", err.Error()), logger.Field("value", v.Discount)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "json.Unmarshal error: %v", err.Error()) - } - } else { - sub.Discount = make([]types.SubscribeDiscount, 0) - } - resp.List = append(resp.List, sub) - } - return -} diff --git a/internal/logic/app/subscribe/queryUserAlreadySubscribeLogic.go b/internal/logic/app/subscribe/queryUserAlreadySubscribeLogic.go deleted file mode 100644 index 330213d..0000000 --- a/internal/logic/app/subscribe/queryUserAlreadySubscribeLogic.go +++ /dev/null @@ -1,67 +0,0 @@ -package subscribe - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" -) - -type QueryUserAlreadySubscribeLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get Already subscribed to package -func NewQueryUserAlreadySubscribeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryUserAlreadySubscribeLogic { - return &QueryUserAlreadySubscribeLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *QueryUserAlreadySubscribeLogic) QueryUserAlreadySubscribe() (resp *types.QueryUserSubscribeResp, err error) { - resp = &types.QueryUserSubscribeResp{ - Data: make([]types.UserSubscribeData, 0), - } - userInfo := l.ctx.Value(constant.CtxKeyUser).(*user.User) - var orderIds []int64 - var subscribes []user.Subscribe - err = l.svcCtx.OrderModel.Transaction(context.Background(), func(tx *gorm.DB) error { - if err := tx.Model(&order.Order{}).Where("user_id = ? AND status in ?", userInfo.Id, []int64{2, 5}).Select("id").Find(&orderIds).Error; err != nil { - return err - } - if len(orderIds) == 0 { - return nil - } - return tx.Model(&user.Subscribe{}).Where("user_id = ? AND order_id in ?", userInfo.Id, orderIds).Order("created_at desc").Find(&subscribes).Error - }) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find order error: %v", err.Error()) - } - if len(subscribes) == 0 { - return - } - - userAlreadySubscribe := make(map[int64]int64) - for _, subscribe := range subscribes { - userAlreadySubscribe[subscribe.SubscribeId] = subscribe.Id - } - - for k, v := range userAlreadySubscribe { - resp.Data = append(resp.Data, types.UserSubscribeData{ - SubscribeId: k, - UserSubscribeId: v, - }) - } - return -} diff --git a/internal/logic/app/subscribe/queryUserAvailableUserSubscribeLogic.go b/internal/logic/app/subscribe/queryUserAvailableUserSubscribeLogic.go deleted file mode 100644 index a41513a..0000000 --- a/internal/logic/app/subscribe/queryUserAvailableUserSubscribeLogic.go +++ /dev/null @@ -1,118 +0,0 @@ -package subscribe - -import ( - "context" - "encoding/json" - "strconv" - "strings" - "time" - - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/countryCenter" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type QueryUserAvailableUserSubscribeLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get Available subscriptions for users -func NewQueryUserAvailableUserSubscribeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryUserAvailableUserSubscribeLogic { - return &QueryUserAvailableUserSubscribeLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *QueryUserAvailableUserSubscribeLogic) QueryUserAvailableUserSubscribe(req *types.AppUserSubscribeRequest) (resp *types.AppUserSubscbribeResponse, err error) { - resp = &types.AppUserSubscbribeResponse{List: make([]types.AppUserSubcbribe, 0)} - userInfo := l.ctx.Value(constant.CtxKeyUser).(*user.User) - //查询用户订阅 - subscribeDetails, err := l.svcCtx.UserModel.QueryUserSubscribe(l.ctx, userInfo.Id, 1, 2) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get query user subscribe error: %v", err.Error()) - } - - userSubscribeMap := make(map[int64]types.AppUserSubcbribe) - for _, sd := range subscribeDetails { - userSubscribeInfo := types.AppUserSubcbribe{ - Id: sd.Id, - Name: sd.Subscribe.Name, - Traffic: sd.Traffic, - Upload: sd.Upload, - Download: sd.Download, - ExpireTime: sd.ExpireTime.Format(time.DateTime), - StartTime: sd.StartTime.Format(time.DateTime), - DeviceLimit: sd.Subscribe.DeviceLimit, - } - - //不需要查询节点 - if req.ContainsNodes == nil || !*req.ContainsNodes { - resp.List = append(resp.List, userSubscribeInfo) - continue - } - - //拿到所有订阅下的服务组id - var ids []int64 - for _, idStr := range strings.Split(sd.Subscribe.ServerGroup, ",") { - id, err := strconv.ParseInt(idStr, 10, 64) - if err != nil { - continue - } - ids = append(ids, id) - } - //根据服务组id拿到所有节点 - servers, err := l.svcCtx.ServerModel.FindServerListByGroupIds(l.ctx, ids) - if err != nil { - l.Logger.Errorf("FindServerListByGroupIds error: %v", err.Error()) - continue - } - - for _, server := range servers { - latitude, longitude, found := countryCenter.GetCountryCenterByCountryOrCity(server.Country, server.City) - if !found { - latitude = server.Latitude - longitude = server.Longitude - } - userSubscribeInfo.List = append(userSubscribeInfo.List, types.AppUserSubscbribeNode{ - Id: server.Id, - Uuid: sd.UUID, - Traffic: sd.Traffic, - Upload: sd.Upload, - Download: sd.Download, - RelayNode: server.RelayNode, - RelayMode: server.RelayMode, - Longitude: server.Longitude, - Latitude: server.Latitude, - LatitudeCountry: latitude, - LongitudeCountry: longitude, - Tags: strings.Split(server.Tags, ","), - Config: server.Config, - ServerAddr: server.ServerAddr, - Protocol: server.Protocol, - SpeedLimit: server.SpeedLimit, - City: server.City, - Country: server.Country, - Name: server.Name, - }) - } - resp.List = append(resp.List, userSubscribeInfo) - userSubscribeMap[userSubscribeInfo.Id] = userSubscribeInfo - } - - for _, userSubscribeInfo := range userSubscribeMap { - resp.List = append(resp.List, userSubscribeInfo) - } - data, _ := json.Marshal(resp) - l.Logger.Infof("QueryUserAvailableUserSubscribe resp: %s", string(data)) - return resp, nil - -} diff --git a/internal/logic/app/subscribe/resetUserSubscribePeriodLogic.go b/internal/logic/app/subscribe/resetUserSubscribePeriodLogic.go deleted file mode 100644 index 1b5e8f5..0000000 --- a/internal/logic/app/subscribe/resetUserSubscribePeriodLogic.go +++ /dev/null @@ -1,60 +0,0 @@ -package subscribe - -import ( - "context" - "time" - - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type ResetUserSubscribePeriodLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewResetUserSubscribePeriodLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResetUserSubscribePeriodLogic { - return &ResetUserSubscribePeriodLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *ResetUserSubscribePeriodLogic) ResetUserSubscribePeriod(req *types.UserSubscribeResetPeriodRequest) (resp *types.UserSubscribeResetPeriodResponse, err error) { - resp = &types.UserSubscribeResetPeriodResponse{} - userInfo := l.ctx.Value(constant.CtxKeyUser).(*user.User) - subscribe, err := l.svcCtx.UserModel.FindOneSubscribe(l.ctx, req.UserSubscribeId) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find order error: %v", err.Error()) - } - if userInfo.Id != subscribe.UserId { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SubscribeNotAvailable), "user not authorized,subscribe not available") - } - - if time.Now().After(subscribe.ExpireTime) { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SubscribeExpired), "subscribe expired") - } - - if subscribe.Traffic < 1 { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ExistAvailableTraffic), "Unlimited data plan.") - } - - if (subscribe.Download + subscribe.Upload + 10240) < subscribe.Traffic { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ExistAvailableTraffic), "There is still available traffic.") - } - - subscribe.ExpireTime = subscribe.ExpireTime.AddDate(0, -1, 0) - err = l.svcCtx.UserModel.UpdateSubscribe(l.ctx, subscribe) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update subscribe error: %v", err.Error()) - } - resp.Status = true - return -} diff --git a/internal/logic/app/user/deleteAccountLogic.go b/internal/logic/app/user/deleteAccountLogic.go deleted file mode 100644 index fdc04e3..0000000 --- a/internal/logic/app/user/deleteAccountLogic.go +++ /dev/null @@ -1,103 +0,0 @@ -package user - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/logic/common" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type DeleteAccountLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Delete Account -func NewDeleteAccountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteAccountLogic { - return &DeleteAccountLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *DeleteAccountLogic) DeleteAccount(req *types.DeleteAccountRequest) error { - userInfo, exists := l.ctx.Value(constant.CtxKeyUser).(user.User) - if !exists { - return nil - } - - var account string - for _, authMethod := range userInfo.AuthMethods { - if authMethod.AuthType == req.Method { - account = authMethod.AuthIdentifier - break - } - } - if account == "" { - return nil - } - - if req.Method == "email" { - emailConfig := l.svcCtx.Config.Email - - if !emailConfig.Enable { - return errors.Wrapf(xerr.NewErrCode(xerr.EmailNotEnabled), "Email function is not enabled yet") - } - - if emailConfig.EnableVerify { - cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, constant.Security, account) - value, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result() - if err != nil { - l.Errorw("Redis Error", logger.Field("error", err.Error()), logger.Field("cacheKey", cacheKey)) - return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - - var payload common.CacheKeyPayload - err = json.Unmarshal([]byte(value), &payload) - if err != nil { - l.Errorw("Unmarshal Error", logger.Field("error", err.Error()), logger.Field("value", value)) - return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - - if payload.Code != req.Code { - return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - } - } else { - cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeTelephoneCacheKey, constant.Security, account) - value, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result() - if err != nil { - l.Errorw("Redis Error", logger.Field("error", err.Error()), logger.Field("cacheKey", cacheKey)) - return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - - if value == "" { - return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - - var payload common.CacheKeyPayload - if err := json.Unmarshal([]byte(value), &payload); err != nil { - l.Errorw("[SendSmsCode]: Unmarshal Error", logger.Field("error", err.Error()), logger.Field("value", value)) - } - if payload.Code != req.Code { - return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") - } - } - err := l.svcCtx.UserModel.Delete(l.ctx, userInfo.Id) - if err != nil { - l.Errorw("update user password error", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update user password") - } - return nil -} diff --git a/internal/logic/app/user/getuseronlinetimestatisticslogic.go b/internal/logic/app/user/getuseronlinetimestatisticslogic.go deleted file mode 100644 index e875dfd..0000000 --- a/internal/logic/app/user/getuseronlinetimestatisticslogic.go +++ /dev/null @@ -1,115 +0,0 @@ -package user - -import ( - "context" - "sort" - "time" - - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -type GetUserOnlineTimeStatisticsLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get user online time total -func NewGetUserOnlineTimeStatisticsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserOnlineTimeStatisticsLogic { - return &GetUserOnlineTimeStatisticsLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *GetUserOnlineTimeStatisticsLogic) GetUserOnlineTimeStatistics() (resp *types.GetUserOnlineTimeStatisticsResponse, err error) { - u := l.ctx.Value(constant.CtxKeyUser).(*user.User) - //获取历史最长在线时间 - var OnlineSeconds int64 - if err := l.svcCtx.DB.Model(user.DeviceOnlineRecord{}).Where("user_id = ?", u.Id).Select("online_seconds").Order("online_seconds desc").Limit(1).Scan(&OnlineSeconds).Error; err != nil { - l.Logger.Error(err) - } - - //获取历史连续最长在线天数 - var DurationDays int64 - if err := l.svcCtx.DB.Model(user.DeviceOnlineRecord{}).Where("user_id = ?", u.Id).Select("duration_days").Order("duration_days desc").Limit(1).Scan(&DurationDays).Error; err != nil { - l.Logger.Error(err) - } - - //获取近七天在线情况 - var userOnlineRecord []user.DeviceOnlineRecord - if err := l.svcCtx.DB.Model(&userOnlineRecord).Where("user_id = ? and created_at >= ?", u.Id, time.Now().AddDate(0, 0, -7).Format(time.DateTime)).Order("created_at desc").Find(&userOnlineRecord).Error; err != nil { - l.Logger.Error(err) - } - - //获取当前连续在线天数 - var currentContinuousDays int64 - if len(userOnlineRecord) > 0 { - currentContinuousDays = userOnlineRecord[0].DurationDays - } else { - currentContinuousDays = 1 - } - - var dates []string - for i := 0; i < 7; i++ { - date := time.Now().AddDate(0, 0, -i).Format(time.DateOnly) - dates = append(dates, date) - } - - onlineDays := make(map[string]types.WeeklyStat) - for _, record := range userOnlineRecord { - //获取近七天在线情况 - onlineTime := record.OnlineTime.Format(time.DateOnly) - if weeklyStat, ok := onlineDays[onlineTime]; ok { - weeklyStat.Hours += float64(record.OnlineSeconds) - onlineDays[onlineTime] = weeklyStat - } else { - onlineDays[onlineTime] = types.WeeklyStat{ - Hours: float64(record.OnlineSeconds), - //根据日期获取周几 - DayName: record.OnlineTime.Weekday().String(), - } - } - } - - //补全不存在的日期 - for _, date := range dates { - if _, ok := onlineDays[date]; !ok { - onlineTime, _ := time.Parse(time.DateOnly, date) - onlineDays[date] = types.WeeklyStat{ - DayName: onlineTime.Weekday().String(), - } - } - } - - var keys []string - for key := range onlineDays { - keys = append(keys, key) - } - - //排序 - sort.Strings(keys) - - var weeklyStats []types.WeeklyStat - for index, key := range keys { - weeklyStat := onlineDays[key] - weeklyStat.Day = index + 1 - weeklyStat.Hours = weeklyStat.Hours / float64(3600) - weeklyStats = append(weeklyStats, weeklyStat) - } - - resp = &types.GetUserOnlineTimeStatisticsResponse{ - WeeklyStats: weeklyStats, - ConnectionRecords: types.ConnectionRecords{ - CurrentContinuousDays: currentContinuousDays, - HistoryContinuousDays: DurationDays, - LongestSingleConnection: OnlineSeconds / 60, - }, - } - return -} diff --git a/internal/logic/app/user/getusersubscribetrafficlogslogic.go b/internal/logic/app/user/getusersubscribetrafficlogslogic.go deleted file mode 100644 index 4c8a196..0000000 --- a/internal/logic/app/user/getusersubscribetrafficlogslogic.go +++ /dev/null @@ -1,85 +0,0 @@ -package user - -import ( - "context" - "time" - - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - - "github.com/perfect-panel/ppanel-server/internal/model/traffic" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "gorm.io/gorm" -) - -type GetUserSubscribeTrafficLogsLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get user subcribe traffic logs -func NewGetUserSubscribeTrafficLogsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserSubscribeTrafficLogsLogic { - return &GetUserSubscribeTrafficLogsLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *GetUserSubscribeTrafficLogsLogic) GetUserSubscribeTrafficLogs(req *types.GetUserSubscribeTrafficLogsRequest) (resp *types.GetUserSubscribeTrafficLogsResponse, err error) { - resp = &types.GetUserSubscribeTrafficLogsResponse{} - u := l.ctx.Value(constant.CtxKeyUser).(*user.User) - var traffics []traffic.TrafficLog - err = l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { - return db.Model(traffic.TrafficLog{}).Where("user_id = ? and `timestamp` >= ? and `timestamp` < ?", u.Id, time.UnixMilli(req.StartTime), time.UnixMilli(req.EndTime)).Find(&traffics).Error - }) - - if err != nil { - l.Errorw("get user subscribe traffic logs failed", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), err.Error()) - } - - //合并多条记录为以天为单位 - trafficMap := make(map[string]*traffic.TrafficLog) - for _, traf := range traffics { - key := traf.Timestamp.Format(time.DateOnly) - existTraf := trafficMap[key] - if existTraf == nil { - trafficMap[key] = &traf - } else { - existTraf.Upload = existTraf.Download + traf.Upload - existTraf.Download = existTraf.Download + traf.Download - trafficMap[key] = existTraf - } - } - - startTime := time.UnixMilli(req.StartTime) - EndTime := time.UnixMilli(req.EndTime) - res := make(map[string]traffic.TrafficLog) - - // 循环遍历每一天 - for current := startTime; !current.After(EndTime); current = current.AddDate(0, 0, 1) { - dateStr := current.Format(time.DateOnly) // 格式化为日期字符串 - if trafficMap[dateStr] == nil { - res[dateStr] = traffic.TrafficLog{ - Timestamp: current, - } - } else { - res[dateStr] = *trafficMap[dateStr] - } - resp.List = append(resp.List, types.TrafficLog{ - Id: res[dateStr].Id, - ServerId: res[dateStr].ServerId, - Upload: res[dateStr].Upload, - Download: res[dateStr].Download, - Timestamp: res[dateStr].Timestamp.UnixMilli(), - }) - } - - return -} diff --git a/internal/logic/app/user/queryUserAffiliateListLogic.go b/internal/logic/app/user/queryUserAffiliateListLogic.go deleted file mode 100644 index 4ef7a41..0000000 --- a/internal/logic/app/user/queryUserAffiliateListLogic.go +++ /dev/null @@ -1,62 +0,0 @@ -package user - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/pkg/constant" - - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -type QueryUserAffiliateListLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Query User Affiliate List -func NewQueryUserAffiliateListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryUserAffiliateListLogic { - return &QueryUserAffiliateListLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *QueryUserAffiliateListLogic) QueryUserAffiliateList(req *types.QueryUserAffiliateListRequest) (resp *types.QueryUserAffiliateListResponse, 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") - } - var data []*user.User - var total int64 - err = l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { - return db.Model(&user.User{}).Order("id desc").Where("referer_id = ?", u.Id).Count(&total).Limit(req.Size).Offset((req.Page - 1) * req.Size).Find(&data).Error - }) - if err != nil { - l.Errorw("Query User Affiliate List failed: %v", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query User Affiliate List failed: %v", err.Error()) - } - - list := make([]types.UserAffiliate, 0) - for _, item := range data { - list = append(list, types.UserAffiliate{ - //Email: tool.MaskEmail(item.Email), - Avatar: item.Avatar, - RegisteredAt: item.CreatedAt.UnixMilli(), - Enable: *item.Enable, - }) - } - return &types.QueryUserAffiliateListResponse{ - Total: total, - List: list, - }, nil -} diff --git a/internal/logic/app/user/queryUserInfoLogic.go b/internal/logic/app/user/queryUserInfoLogic.go deleted file mode 100644 index 76fea78..0000000 --- a/internal/logic/app/user/queryUserInfoLogic.go +++ /dev/null @@ -1,63 +0,0 @@ -package user - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/pkg/constant" - - "github.com/perfect-panel/ppanel-server/internal/model/user" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -type QueryUserInfoLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// query user info -func NewQueryUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryUserInfoLogic { - return &QueryUserInfoLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *QueryUserInfoLogic) QueryUserInfo() (resp *types.UserInfoResponse, err error) { - u := l.ctx.Value(constant.CtxKeyUser).(*user.User) - var devices []types.UserDevice - if len(u.UserDevices) != 0 { - for _, device := range u.UserDevices { - devices = append(devices, types.UserDevice{ - Id: device.Id, - Identifier: device.Identifier, - Online: device.Online, - }) - } - } - var authMeths []types.UserAuthMethod - authMethods, err := l.svcCtx.UserModel.FindUserAuthMethods(l.ctx, u.Id) - if err == nil && len(authMeths) != 0 { - for _, as := range authMethods { - authMeths = append(authMeths, types.UserAuthMethod{ - AuthType: as.AuthType, - AuthIdentifier: as.AuthIdentifier, - }) - } - } - - resp = &types.UserInfoResponse{ - Id: u.Id, - Balance: u.Balance, - Avatar: u.Avatar, - ReferCode: u.ReferCode, - RefererId: u.RefererId, - Devices: devices, - AuthMethods: authMeths, - } - return -} diff --git a/internal/logic/app/user/queryuseraffiliatelogic.go b/internal/logic/app/user/queryuseraffiliatelogic.go deleted file mode 100644 index 839a81d..0000000 --- a/internal/logic/app/user/queryuseraffiliatelogic.go +++ /dev/null @@ -1,60 +0,0 @@ -package user - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -type QueryUserAffiliateLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Query User Affiliate Count -func NewQueryUserAffiliateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryUserAffiliateLogic { - return &QueryUserAffiliateLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *QueryUserAffiliateLogic) QueryUserAffiliate() (resp *types.QueryUserAffiliateCountResponse, 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") - } - var sum int64 - var total int64 - err = l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { - return db.Model(&user.User{}).Where("referer_id = ?", u.Id).Count(&total).Find(&user.User{}).Error - }) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query User Affiliate failed: %v", err) - } - err = l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { - return db.Model(&user.CommissionLog{}). - Where("user_id = ?", u.Id). - Select("COALESCE(SUM(amount), 0)"). - Scan(&sum).Error - }) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query User Affiliate failed: %v", err) - } - - return &types.QueryUserAffiliateCountResponse{ - Registers: total, - TotalCommission: sum, - }, nil -} diff --git a/internal/logic/app/user/updatePasswordLogic.go b/internal/logic/app/user/updatePasswordLogic.go deleted file mode 100644 index 073600b..0000000 --- a/internal/logic/app/user/updatePasswordLogic.go +++ /dev/null @@ -1,46 +0,0 @@ -package user - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/pkg/constant" - - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type UpdatePasswordLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Update Password -func NewUpdatePasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdatePasswordLogic { - return &UpdatePasswordLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *UpdatePasswordLogic) UpdatePassword(req *types.UpdatePasswordRequeset) error { - userInfo := l.ctx.Value(constant.CtxKeyUser).(*user.User) - - // Verify password - if !tool.VerifyPassWord(req.Password, userInfo.Password) { - return errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password") - } - userInfo.Password = tool.EncodePassWord(req.NewPassword) - err := l.svcCtx.UserModel.Update(l.ctx, userInfo) - if err != nil { - l.Errorw("update user password error", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update user password") - } - return err -} diff --git a/internal/logic/app/ws/appWsLogic.go b/internal/logic/app/ws/appWsLogic.go deleted file mode 100644 index 8f474da..0000000 --- a/internal/logic/app/ws/appWsLogic.go +++ /dev/null @@ -1,81 +0,0 @@ -package ws - -import ( - "context" - "net/http" - "strconv" - "time" - - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -type AppWsLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// App heartbeat -func NewAppWsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AppWsLogic { - return &AppWsLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *AppWsLogic) AppWs(w http.ResponseWriter, r *http.Request, userid, identifier string) error { - //获取设备号 - if identifier == "" { - return xerr.NewErrCode(xerr.DeviceNotExist) - } - //获取用户id - userID, err := strconv.ParseInt(userid, 10, 64) - if err != nil { - return xerr.NewErrCode(xerr.UseridNotMatch) - } - - ////获取session - value := l.ctx.Value(constant.CtxKeySessionID) - if value == nil { - return xerr.NewErrCode(xerr.ErrorTokenInvalid) - } - session := value.(string) - - //获取用户 - userInfo := l.ctx.Value(constant.CtxKeyUser).(*user.User) - - if userID != userInfo.Id { - return xerr.NewErrCode(xerr.UseridNotMatch) - } - - _, err = l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, identifier) - if err != nil { - return xerr.NewErrCode(xerr.DeviceNotExist) - } - - //if device.UserId != userInfo.Id { - // return xerr.NewErrCode(xerr.DeviceNotExist) - //} - - //默认在线设备1 - maxDevice := 3 - subscribe, err := l.svcCtx.UserModel.QueryUserSubscribe(l.ctx, userInfo.Id, 1, 2) - if err == nil { - for _, sub := range subscribe { - if time.Now().Before(sub.ExpireTime) { - deviceLimit := int(sub.Subscribe.DeviceLimit) - if deviceLimit > maxDevice { - maxDevice = deviceLimit - } - } - } - } - l.svcCtx.DeviceManager.AddDevice(w, r, session, userID, identifier, maxDevice) - return nil -} diff --git a/internal/logic/auth/bindDeviceLogic.go b/internal/logic/auth/bindDeviceLogic.go new file mode 100644 index 0000000..19b0666 --- /dev/null +++ b/internal/logic/auth/bindDeviceLogic.go @@ -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 +} diff --git a/internal/logic/auth/checkUserLogic.go b/internal/logic/auth/checkUserLogic.go index c1503b8..a3727d2 100644 --- a/internal/logic/auth/checkUserLogic.go +++ b/internal/logic/auth/checkUserLogic.go @@ -3,10 +3,10 @@ package auth import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" "gorm.io/gorm" ) diff --git a/internal/logic/auth/checkUserTelephoneLogic.go b/internal/logic/auth/checkUserTelephoneLogic.go index 9c42fc6..92aab10 100644 --- a/internal/logic/auth/checkUserTelephoneLogic.go +++ b/internal/logic/auth/checkUserTelephoneLogic.go @@ -3,14 +3,14 @@ package auth import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/phone" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/pkg/phone" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type CheckUserTelephoneLogic struct { diff --git a/internal/logic/auth/deviceLoginLogic.go b/internal/logic/auth/deviceLoginLogic.go new file mode 100644 index 0000000..8f7807a --- /dev/null +++ b/internal/logic/auth/deviceLoginLogic.go @@ -0,0 +1,295 @@ +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) { + //TODO : check device login rate limit + // Check if device login is enabled + //if !l.svcCtx.Config.Register.EnableDevice { + // 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 +} diff --git a/internal/logic/auth/oauth/appleLoginCallbackLogic.go b/internal/logic/auth/oauth/appleLoginCallbackLogic.go index 38c825f..333c9cd 100644 --- a/internal/logic/auth/oauth/appleLoginCallbackLogic.go +++ b/internal/logic/auth/oauth/appleLoginCallbackLogic.go @@ -6,9 +6,9 @@ import ( "net/http" "net/url" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type AppleLoginCallbackLogic struct { diff --git a/internal/logic/auth/oauth/oAuthLoginGetTokenLogic.go b/internal/logic/auth/oauth/oAuthLoginGetTokenLogic.go index 4fd9833..4e12d2f 100644 --- a/internal/logic/auth/oauth/oAuthLoginGetTokenLogic.go +++ b/internal/logic/auth/oauth/oAuthLoginGetTokenLogic.go @@ -6,24 +6,34 @@ import ( "fmt" "time" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/auth" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/jwt" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/oauth/apple" - "github.com/perfect-panel/ppanel-server/pkg/oauth/google" - "github.com/perfect-panel/ppanel-server/pkg/oauth/telegram" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/auth" + "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/oauth/apple" + "github.com/perfect-panel/server/pkg/oauth/google" + "github.com/perfect-panel/server/pkg/oauth/telegram" + "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 googleRequest struct { +const ( + OAuthGoogle = "google" + OAuthApple = "apple" + OAuthTelegram = "telegram" + AuthEmail = "email" + AuthExpire = 86400 + TelegramDomain = "ppanel.com" +) + +type oauthRequest struct { Code string `json:"code"` State string `json:"state"` } @@ -43,38 +53,524 @@ func NewOAuthLoginGetTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) } func (l *OAuthLoginGetTokenLogic) OAuthLoginGetToken(req *types.OAuthLoginGetTokenRequest, ip, userAgent string) (resp *types.LoginResponse, err error) { + requestID := uuidx.NewUUID().String() loginStatus := false var userInfo *user.User - // Record login status - defer func(svcCtx *svc.ServiceContext) { - if userInfo != nil && userInfo.Id != 0 { - if err := svcCtx.UserModel.InsertLoginLog(l.ctx, &user.LoginLog{ - UserId: userInfo.Id, - LoginIP: ip, - UserAgent: userAgent, - Success: &loginStatus, - }); err != nil { - l.Errorw("error insert login log: %v", logger.Field("error", err.Error())) - } - } - }(l.svcCtx) - switch req.Method { - case "google": - userInfo, err = l.google(req) - case "apple": - userInfo, err = l.apple(req) - case "telegram": - userInfo, err = l.telegram(req) - default: - l.Errorw("oauth login method not support: %v", logger.Field("method", req.Method)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "oauth login method not support: %v", req.Method) - } + + l.Infow("oauth login request started", + logger.Field("request_id", requestID), + logger.Field("method", req.Method), + logger.Field("ip", ip), + logger.Field("user_agent", userAgent), + ) + + defer func() { + l.recordLoginStatus(loginStatus, userInfo, ip, userAgent, requestID, req.Method) + }() + + userInfo, err = l.handleOAuthProvider(req, requestID, ip, userAgent) if err != nil { return nil, err } - // Generate session id + + token, err := l.generateToken(userInfo, requestID) + if err != nil { + return nil, err + } + + loginStatus = true + return &types.LoginResponse{Token: token}, nil +} + +func (l *OAuthLoginGetTokenLogic) google(req *types.OAuthLoginGetTokenRequest, requestID, ip, userAgent string) (*user.User, error) { + startTime := time.Now() + l.Infow("google oauth processing started", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthGoogle), + ) + + var request oauthRequest + if err := tool.CloneMapToStruct(req.Callback.(map[string]interface{}), &request); err != nil { + l.Errorw("failed to parse google callback data", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthGoogle), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "parse callback data failed: %v", err) + } + + l.Debugw("google oauth state validation started", + logger.Field("request_id", requestID), + logger.Field("state", request.State), + ) + + redirect, err := l.validateStateCode(OAuthGoogle, request.State, requestID) + if err != nil { + return nil, err + } + + cfg, err := l.getGoogleConfig(requestID) + if err != nil { + return nil, err + } + + client := google.New(&google.Config{ + ClientID: cfg.ClientId, + ClientSecret: cfg.ClientSecret, + RedirectURL: redirect, + }) + + l.Debugw("exchanging google authorization code for token", + logger.Field("request_id", requestID), + logger.Field("redirect_url", redirect), + ) + + token, err := client.Exchange(l.ctx, request.Code) + if err != nil { + l.Errorw("failed to exchange google authorization code", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthGoogle), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "exchange token failed: %v", err) + } + + l.Debugw("fetching google user information", + logger.Field("request_id", requestID), + ) + + googleUserInfo, err := client.GetUserInfo(token.AccessToken) + if err != nil { + l.Errorw("failed to get google user info", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthGoogle), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "get user info failed: %v", err) + } + + l.Infow("google oauth processing completed", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthGoogle), + logger.Field("openid", googleUserInfo.OpenID), + logger.Field("email", googleUserInfo.Email), + logger.Field("duration_ms", time.Since(startTime).Milliseconds()), + ) + + return l.findOrRegisterUser(OAuthGoogle, googleUserInfo.OpenID, googleUserInfo.Email, googleUserInfo.Picture, requestID, ip, userAgent) +} + +func (l *OAuthLoginGetTokenLogic) apple(req *types.OAuthLoginGetTokenRequest, requestID, ip, userAgent string) (*user.User, error) { + startTime := time.Now() + l.Infow("apple oauth processing started", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthApple), + ) + + callback := req.Callback.(map[string]interface{}) + state, _ := callback["state"].(string) + code, _ := callback["code"].(string) + + l.Debugw("apple oauth state validation started", + logger.Field("request_id", requestID), + logger.Field("state", state), + ) + + if _, err := l.validateStateCode(OAuthApple, state, requestID); err != nil { + return nil, err + } + + cfg, err := l.getAppleConfig(requestID) + if err != nil { + return nil, err + } + + client, err := apple.New(apple.Config{ + ClientID: cfg.ClientId, + TeamID: cfg.TeamID, + KeyID: cfg.KeyID, + ClientSecret: cfg.ClientSecret, + RedirectURI: cfg.RedirectURL, + }) + if err != nil { + l.Errorw("failed to create apple client", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthApple), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "new apple client failed: %v", err) + } + + l.Debugw("verifying apple web token", + logger.Field("request_id", requestID), + ) + + resp, err := client.VerifyWebToken(l.ctx, code) + if err != nil { + l.Errorw("failed to verify apple web token", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthApple), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "verify web token failed: %v", err) + } + + if resp.Error != "" { + l.Errorw("apple web token verification returned error", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthApple), + logger.Field("apple_error", resp.Error), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "verify web token failed: %v", resp.Error) + } + + appleUnique, err := apple.GetUniqueID(resp.IDToken) + if err != nil { + l.Errorw("failed to get apple unique id", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthApple), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "get apple unique id failed: %v", err) + } + + appleUserInfo, err := apple.GetClaims(resp.AccessToken) + if err != nil { + l.Errorw("failed to get apple user claims", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthApple), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "get apple user info failed: %v", err) + } + + email := "" + if emailVal, ok := (*appleUserInfo)["email"]; ok { + email, _ = emailVal.(string) + } + + l.Infow("apple oauth processing completed", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthApple), + logger.Field("unique_id", appleUnique), + logger.Field("email", email), + logger.Field("duration_ms", time.Since(startTime).Milliseconds()), + ) + + return l.findOrRegisterUser(OAuthApple, appleUnique, email, "", requestID, ip, userAgent) +} + +func (l *OAuthLoginGetTokenLogic) telegram(req *types.OAuthLoginGetTokenRequest, requestID, ip, userAgent string) (*user.User, error) { + startTime := time.Now() + l.Infow("telegram oauth processing started", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthTelegram), + ) + + cfg, err := l.getTelegramConfig(requestID) + if err != nil { + return nil, err + } + + encodeText, _ := req.Callback.(map[string]interface{})["tgAuthResult"].(string) + l.Debugw("parsing telegram callback data", + logger.Field("request_id", requestID), + logger.Field("data_length", len(encodeText)), + ) + + callbackData, err := telegram.ParseAndValidateBase64([]byte(encodeText), cfg.BotToken) + if err != nil { + l.Errorw("failed to parse telegram callback data", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthTelegram), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "parse telegram callback failed: %v", err) + } + + l.Debugw("validating telegram auth date", + logger.Field("request_id", requestID), + logger.Field("auth_date", *callbackData.AuthDate), + logger.Field("current_time", time.Now().Unix()), + ) + + if time.Now().Unix()-*callbackData.AuthDate > AuthExpire { + l.Errorw("telegram auth date expired", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthTelegram), + logger.Field("auth_date", *callbackData.AuthDate), + logger.Field("current_time", time.Now().Unix()), + logger.Field("expire_seconds", AuthExpire), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "auth date expired") + } + + userID := fmt.Sprintf("%v", *callbackData.Id) + email := fmt.Sprintf("%v@%s", *callbackData.Id, TelegramDomain) + avatar := "" + if callbackData.PhotoUrl != nil { + avatar = *callbackData.PhotoUrl + } + + l.Infow("telegram oauth processing completed", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthTelegram), + logger.Field("user_id", userID), + logger.Field("email", email), + logger.Field("duration_ms", time.Since(startTime).Milliseconds()), + ) + + return l.findOrRegisterUser(OAuthTelegram, userID, email, avatar, requestID, ip, userAgent) +} + +func (l *OAuthLoginGetTokenLogic) register(email, avatar, method, openid, requestID, ip, userAgent string) (*user.User, error) { + startTime := time.Now() + l.Infow("user registration started", + logger.Field("request_id", requestID), + logger.Field("auth_method", method), + logger.Field("email", email), + logger.Field("openid", openid), + ) + + if l.svcCtx.Config.Invite.ForcedInvite { + l.Errorw("registration blocked due to forced invite policy", + logger.Field("request_id", requestID), + logger.Field("auth_method", method), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.InviteCodeError), "invite code is required") + } + + var userInfo *user.User + err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { + if email != "" { + l.Debugw("checking if email already exists", + logger.Field("request_id", requestID), + logger.Field("email", email), + ) + if err := l.checkEmailExists(db, email, requestID); err != nil { + return err + } + } + + l.Debugw("creating new user record", + logger.Field("request_id", requestID), + logger.Field("avatar", avatar), + ) + + userInfo = &user.User{Avatar: avatar, OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase} + if err := db.Create(userInfo).Error; err != nil { + l.Errorw("failed to create user record", + logger.Field("request_id", requestID), + logger.Field("error", err.Error()), + ) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create user info failed: %v", err) + } + + userInfo.ReferCode = uuidx.UserInviteCode(userInfo.Id) + l.Debugw("updating user refer code", + logger.Field("request_id", requestID), + logger.Field("user_id", userInfo.Id), + logger.Field("refer_code", userInfo.ReferCode), + ) + + 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("request_id", requestID), + logger.Field("user_id", userInfo.Id), + logger.Field("error", err.Error()), + ) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update refer code failed: %v", err) + } + + if err := l.createAuthMethod(db, userInfo.Id, method, openid, requestID); err != nil { + return err + } + + if email != "" { + if err := l.createAuthMethod(db, userInfo.Id, AuthEmail, email, requestID); err != nil { + return err + } + } + + if l.svcCtx.Config.Register.EnableTrial { + l.Debugw("activating trial subscription", + logger.Field("request_id", requestID), + logger.Field("user_id", userInfo.Id), + ) + if err := l.activeTrial(userInfo.Id, requestID); err != nil { + return err + } + } + + return nil + }) + + if err != nil { + l.Errorw("user registration failed", + logger.Field("request_id", requestID), + logger.Field("auth_method", method), + logger.Field("error", err.Error()), + logger.Field("duration_ms", time.Since(startTime).Milliseconds()), + ) + return userInfo, err + } + + l.Infow("user registration completed successfully", + logger.Field("request_id", requestID), + logger.Field("user_id", userInfo.Id), + logger.Field("auth_method", method), + logger.Field("email", email), + logger.Field("refer_code", userInfo.ReferCode), + logger.Field("duration_ms", time.Since(startTime).Milliseconds()), + ) + + // Register log + registerLog := log.Register{ + AuthMethod: method, + Identifier: openid, + RegisterIP: ip, + UserAgent: userAgent, + Timestamp: time.Now().UnixMilli(), + } + content, _ := registerLog.Marshal() + + 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), + }) + if err != nil { + l.Errorw("failed to insert register log", + logger.Field("request_id", requestID), + logger.Field("user_id", userInfo.Id), + logger.Field("ip", ip), + logger.Field("error", err.Error()), + ) + } + + return userInfo, err +} + +func (l *OAuthLoginGetTokenLogic) checkEmailExists(db *gorm.DB, email, requestID string) error { + var methodInfo user.AuthMethods + err := db.Model(&user.AuthMethods{}).Where("auth_identifier = ?", email).First(&methodInfo).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + l.Errorw("failed to check email existence", + logger.Field("request_id", requestID), + logger.Field("email", email), + logger.Field("error", err.Error()), + ) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "check email exists failed: %v", err) + } + if methodInfo.UserId != 0 { + l.Errorw("email already exists for another user", + logger.Field("request_id", requestID), + logger.Field("email", email), + logger.Field("existing_user_id", methodInfo.UserId), + ) + return errors.Wrapf(xerr.NewErrCode(xerr.UserExist), "user email exist: %v", email) + } + l.Debugw("email availability confirmed", + logger.Field("request_id", requestID), + logger.Field("email", email), + ) + return nil +} + +func (l *OAuthLoginGetTokenLogic) createAuthMethod(db *gorm.DB, userID int64, authType, identifier, requestID string) error { + l.Debugw("creating auth method", + logger.Field("request_id", requestID), + logger.Field("user_id", userID), + logger.Field("auth_type", authType), + logger.Field("identifier", identifier), + ) + + authMethod := &user.AuthMethods{ + UserId: userID, + AuthType: authType, + AuthIdentifier: identifier, + Verified: true, + } + if err := db.Create(authMethod).Error; err != nil { + l.Errorw("failed to create auth method", + logger.Field("request_id", requestID), + logger.Field("user_id", userID), + logger.Field("auth_type", authType), + logger.Field("identifier", identifier), + logger.Field("error", err.Error()), + ) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create auth method failed: %v", err) + } + + l.Debugw("auth method created successfully", + logger.Field("request_id", requestID), + logger.Field("user_id", userID), + logger.Field("auth_type", authType), + logger.Field("auth_method_id", authMethod.Id), + ) + return nil +} + +func (l *OAuthLoginGetTokenLogic) recordLoginStatus(loginStatus bool, userInfo *user.User, ip, userAgent, requestID, authType string) { + + if userInfo != nil && userInfo.Id != 0 { + loginLog := log.Login{ + Method: authType, + LoginIP: ip, + UserAgent: 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("request_id", requestID), + logger.Field("user_id", userInfo.Id), + logger.Field("ip", ip), + logger.Field("error", err.Error()), + ) + } + } +} + +func (l *OAuthLoginGetTokenLogic) handleOAuthProvider(req *types.OAuthLoginGetTokenRequest, requestID, ip, userAgent string) (*user.User, error) { + l.Debugw("handling oauth provider", + logger.Field("request_id", requestID), + logger.Field("provider", req.Method), + ) + + switch req.Method { + case OAuthGoogle: + return l.google(req, requestID, ip, userAgent) + case OAuthApple: + return l.apple(req, requestID, ip, userAgent) + case OAuthTelegram: + return l.telegram(req, requestID, ip, userAgent) + default: + l.Errorw("unsupported oauth login method", + logger.Field("request_id", requestID), + logger.Field("method", req.Method), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "oauth login method not supported: %v", req.Method) + } +} + +func (l *OAuthLoginGetTokenLogic) generateToken(userInfo *user.User, requestID string) (string, error) { + startTime := time.Now() sessionId := uuidx.NewUUID().String() - // Generate token + + l.Debugw("generating jwt token", + logger.Field("request_id", requestID), + logger.Field("user_id", userInfo.Id), + logger.Field("session_id", sessionId), + ) + token, err := jwt.NewJwtToken( l.svcCtx.Config.JwtAuth.AccessSecret, time.Now().Unix(), @@ -83,262 +579,284 @@ func (l *OAuthLoginGetTokenLogic) OAuthLoginGetToken(req *types.OAuthLoginGetTok jwt.WithOption("SessionId", sessionId), ) if err != nil { - l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "token generate error: %v", err.Error()) + l.Errorw("failed to generate jwt token", + logger.Field("request_id", requestID), + logger.Field("user_id", userInfo.Id), + logger.Field("error", err.Error()), + ) + return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "token generate error: %v", err) } 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 { - 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 *OAuthLoginGetTokenLogic) google(req *types.OAuthLoginGetTokenRequest) (*user.User, error) { - var request googleRequest - err := tool.CloneMapToStruct(req.Callback.(map[string]interface{}), &request) - if err != nil { - l.Errorw("error CloneMapToStruct: %v", logger.Field("error", err.Error())) - return nil, err - } - // validate the state code - redirect, err := l.svcCtx.Redis.Get(l.ctx, fmt.Sprintf("google:%s", request.State)).Result() - if err != nil { - l.Errorw("error get google state code: %v", logger.Field("error", err.Error())) - return nil, err - } - // get google config - authMethod, err := l.svcCtx.AuthModel.FindOneByMethod(l.ctx, "google") - if err != nil { - l.Errorw("error find google auth method: %v", logger.Field("error", err.Error())) - return nil, err - } - var cfg auth.GoogleAuthConfig - err = cfg.Unmarshal(authMethod.Config) - if err != nil { - l.Errorw("error unmarshal google config: %v", logger.Field("config", authMethod.Config), logger.Field("error", err.Error())) - return nil, err - } - client := google.New(&google.Config{ - ClientID: cfg.ClientId, - ClientSecret: cfg.ClientSecret, - RedirectURL: redirect, - }) - token, err := client.Exchange(l.ctx, request.Code) - if err != nil { - l.Errorw("error exchange google token: %v", logger.Field("error", err.Error())) - return nil, err - } - googleUserInfo, err := client.GetUserInfo(token.AccessToken) - if err != nil { - l.Errorw("error get google user info: %v", logger.Field("error", err.Error())) - return nil, err - } - // query user info - userAuthMethod, err := l.svcCtx.UserModel.FindUserAuthMethodByOpenID(l.ctx, "google", googleUserInfo.OpenID) - if err != nil { - if errors.As(err, &gorm.ErrRecordNotFound) { - return l.register(googleUserInfo.Email, googleUserInfo.Picture, "google", googleUserInfo.OpenID) - } - return nil, err - } - return l.svcCtx.UserModel.FindOne(l.ctx, userAuthMethod.UserId) -} - -func (l *OAuthLoginGetTokenLogic) apple(req *types.OAuthLoginGetTokenRequest) (*user.User, error) { - // validate the state code - _, err := l.svcCtx.Redis.Get(l.ctx, fmt.Sprintf("apple:%s", req.Callback.(map[string]interface{})["state"])).Result() - if err != nil { - l.Errorw("[AppleLoginCallback] Get State code error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "get apple state code failed: %v", err.Error()) - } - appleAuth, err := l.svcCtx.AuthModel.FindOneByMethod(l.ctx, "apple") - if err != nil { - l.Errorw("[AppleLoginCallback] FindOneByMethod error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find apple auth method failed: %v", err.Error()) - } - var appleCfg auth.AppleAuthConfig - err = appleCfg.Unmarshal(appleAuth.Config) - if err != nil { - l.Errorw("[AppleLoginCallback] Unmarshal error", logger.Field("error", err.Error()), logger.Field("config", appleAuth.Config)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "unmarshal apple config failed: %v", err.Error()) - } - - client, err := apple.New(apple.Config{ - ClientID: appleCfg.ClientId, - TeamID: appleCfg.TeamID, - KeyID: appleCfg.KeyID, - ClientSecret: appleCfg.ClientSecret, - RedirectURI: appleCfg.RedirectURL, - }) - if err != nil { - l.Errorw("[AppleLoginCallback] New apple client error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "new apple client failed: %v", err.Error()) - } - // verify web token - resp, err := client.VerifyWebToken(l.ctx, req.Callback.(map[string]interface{})["code"].(string)) - if err != nil { - l.Errorw("[AppleLoginCallback] VerifyWebToken error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "verify web token failed: %v", err.Error()) - } - if resp.Error != "" { - l.Errorw("[AppleLoginCallback] VerifyWebToken error", logger.Field("error", resp.Error)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "verify web token failed: %v", resp.Error) - } - // query apple user unique id - appleUnique, err := apple.GetUniqueID(resp.IDToken) - if err != nil { - l.Errorw("[AppleLoginCallback] GetUniqueID error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "get apple unique id failed: %v", err.Error()) - } - // get apple user info - appleUserInfo, err := apple.GetClaims(resp.AccessToken) - if err != nil { - l.Errorw("[AppleLoginCallback] GetClaims error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "get apple user info failed: %v", err.Error()) - } - // query user by apple unique id - userAuthMethod, err := l.svcCtx.UserModel.FindUserAuthMethodByOpenID(l.ctx, "apple", appleUnique) - if err != nil { - // if user not exist, handle register - if errors.Is(err, gorm.ErrRecordNotFound) { - return l.register((*appleUserInfo)["email"].(string), "", "apple", appleUnique) - } - l.Errorw("[AppleLoginCallback] FindUserAuthMethodByOpenID error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find user auth method by openid failed: %v", err.Error()) - } - // query user info - userInfo, err := l.svcCtx.UserModel.FindOne(l.ctx, userAuthMethod.UserId) - - if err != nil { - l.Errorw( - "[AppleLoginCallback] FindOne error", + l.Errorw("failed to cache session id", + logger.Field("request_id", requestID), + logger.Field("user_id", userInfo.Id), + logger.Field("session_id", sessionId), logger.Field("error", err.Error()), ) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find user info failed: %v", err.Error()) + return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "set session id error: %v", err) } - return userInfo, nil + l.Infow("jwt token generated successfully", + logger.Field("request_id", requestID), + logger.Field("user_id", userInfo.Id), + logger.Field("session_id", sessionId), + logger.Field("duration_ms", time.Since(startTime).Milliseconds()), + ) + + return token, nil } -func (l *OAuthLoginGetTokenLogic) telegram(req *types.OAuthLoginGetTokenRequest) (*user.User, error) { - appleAuth, err := l.svcCtx.AuthModel.FindOneByMethod(l.ctx, "telegram") +func (l *OAuthLoginGetTokenLogic) validateStateCode(provider, state, requestID string) (string, error) { + stateKey := fmt.Sprintf("%s:%s", provider, state) + l.Debugw("validating oauth state code", + logger.Field("request_id", requestID), + logger.Field("provider", provider), + logger.Field("state_key", stateKey), + ) + + redirect, err := l.svcCtx.Redis.Get(l.ctx, stateKey).Result() if err != nil { - l.Errorw("[OAuthLoginGetToken] FindOneByMethod error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find telegram auth method failed: %v", err.Error()) + l.Errorw("failed to validate state code", + logger.Field("request_id", requestID), + logger.Field("provider", provider), + logger.Field("state_key", stateKey), + logger.Field("error", err.Error()), + ) + return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "get %s state code failed: %v", provider, err) } - var telegramCfg auth.TelegramAuthConfig - err = json.Unmarshal([]byte(appleAuth.Config), &telegramCfg) + + l.Debugw("state code validated successfully", + logger.Field("request_id", requestID), + logger.Field("provider", provider), + logger.Field("redirect_url", redirect), + ) + return redirect, nil +} + +func (l *OAuthLoginGetTokenLogic) getGoogleConfig(requestID string) (*auth.GoogleAuthConfig, error) { + l.Debugw("fetching google oauth config", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthGoogle), + ) + + authMethod, err := l.svcCtx.AuthModel.FindOneByMethod(l.ctx, OAuthGoogle) if err != nil { - l.Errorw("[OAuthLoginGetToken] Unmarshal error", logger.Field("error", err.Error()), logger.Field("config", appleAuth.Config)) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "unmarshal telegram config failed: %v", err.Error()) + l.Errorw("failed to find google auth method", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthGoogle), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find google auth method failed: %v", err) } - encodeText := req.Callback.(map[string]interface{})["tgAuthResult"].(string) - // base64 decode - callbackData, err := telegram.ParseAndValidateBase64([]byte(encodeText), telegramCfg.BotToken) + + var cfg auth.GoogleAuthConfig + if err = cfg.Unmarshal(authMethod.Config); err != nil { + l.Errorw("failed to unmarshal google config", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthGoogle), + logger.Field("config", authMethod.Config), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "unmarshal google config failed: %v", err) + } + + l.Debugw("google oauth config loaded successfully", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthGoogle), + logger.Field("client_id", cfg.ClientId), + ) + return &cfg, nil +} + +func (l *OAuthLoginGetTokenLogic) getAppleConfig(requestID string) (*auth.AppleAuthConfig, error) { + l.Debugw("fetching apple oauth config", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthApple), + ) + + authMethod, err := l.svcCtx.AuthModel.FindOneByMethod(l.ctx, OAuthApple) if err != nil { - l.Errorw("[TelegramLoginCallback] ParseAndValidateBase64 error", logger.Field("error", err.Error())) - return nil, err + l.Errorw("failed to find apple auth method", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthApple), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find apple auth method failed: %v", err) } - // 验证数据有效期 - if time.Now().Unix()-*callbackData.AuthDate > 86400 { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "auth date expired") + + var cfg auth.AppleAuthConfig + if err = cfg.Unmarshal(authMethod.Config); err != nil { + l.Errorw("failed to unmarshal apple config", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthApple), + logger.Field("config", authMethod.Config), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "unmarshal apple config failed: %v", err) } - // query user auth info - userAuthMethod, err := l.svcCtx.UserModel.FindUserAuthMethodByOpenID(l.ctx, "telegram", fmt.Sprintf("%v", *callbackData.Id)) + + l.Debugw("apple oauth config loaded successfully", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthApple), + logger.Field("client_id", cfg.ClientId), + logger.Field("team_id", cfg.TeamID), + ) + return &cfg, nil +} + +func (l *OAuthLoginGetTokenLogic) getTelegramConfig(requestID string) (*auth.TelegramAuthConfig, error) { + l.Debugw("fetching telegram oauth config", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthTelegram), + ) + + authMethod, err := l.svcCtx.AuthModel.FindOneByMethod(l.ctx, OAuthTelegram) if err != nil { - if errors.As(err, &gorm.ErrRecordNotFound) { - return l.register(fmt.Sprintf("%v@%s", *callbackData.Id, "qq.com"), *callbackData.PhotoUrl, "telegram", fmt.Sprintf("%v", callbackData.Id)) + l.Errorw("failed to find telegram auth method", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthTelegram), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find telegram auth method failed: %v", err) + } + + var cfg auth.TelegramAuthConfig + if err = json.Unmarshal([]byte(authMethod.Config), &cfg); err != nil { + l.Errorw("failed to unmarshal telegram config", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthTelegram), + logger.Field("config", authMethod.Config), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "unmarshal telegram config failed: %v", err) + } + + l.Debugw("telegram oauth config loaded successfully", + logger.Field("request_id", requestID), + logger.Field("provider", OAuthTelegram), + ) + return &cfg, nil +} + +func (l *OAuthLoginGetTokenLogic) findOrRegisterUser(authType, openID, email, avatar, requestID, ip, userAgent string) (*user.User, error) { + l.Debugw("finding or registering user", + logger.Field("request_id", requestID), + logger.Field("auth_type", authType), + logger.Field("openid", openID), + logger.Field("email", email), + ) + + userAuthMethod, err := l.svcCtx.UserModel.FindUserAuthMethodByOpenID(l.ctx, authType, openID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + l.Infow("user not found, starting registration", + logger.Field("request_id", requestID), + logger.Field("auth_type", authType), + logger.Field("openid", openID), + logger.Field("email", email), + ) + return l.register(email, avatar, authType, openID, requestID, ip, userAgent) } + l.Errorw("failed to find user auth method by openid", + logger.Field("request_id", requestID), + logger.Field("auth_type", authType), + logger.Field("openid", openID), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find user auth method by openid failed: %v", err) } - // query user info + + l.Debugw("found existing user auth method", + logger.Field("request_id", requestID), + logger.Field("auth_type", authType), + logger.Field("user_id", userAuthMethod.UserId), + ) + userInfo, err := l.svcCtx.UserModel.FindOne(l.ctx, userAuthMethod.UserId) if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find user info failed: %v", err.Error()) + l.Errorw("failed to find user by id", + logger.Field("request_id", requestID), + logger.Field("user_id", userAuthMethod.UserId), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find user info failed: %v", err) } + + l.Infow("existing user found successfully", + logger.Field("request_id", requestID), + logger.Field("user_id", userInfo.Id), + logger.Field("auth_type", authType), + ) + return userInfo, nil } -func (l *OAuthLoginGetTokenLogic) register(email, avatar, method, openid string) (*user.User, error) { - if l.svcCtx.Config.Invite.ForcedInvite { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.InviteCodeError), "invite code is required") - } - var userInfo *user.User - err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { - err := db.Model(&user.User{}).Where("email = ?", email).First(&userInfo).Error - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return err - } - if userInfo.Id != 0 { - return errors.Wrapf(xerr.NewErrCode(xerr.UserExist), "user email exist: %v", email) - } - userInfo = &user.User{ - Avatar: avatar, - } - if err := db.Create(userInfo).Error; err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create user info failed: %v", err.Error()) - } - // Generate ReferCode - userInfo.ReferCode = uuidx.UserInviteCode(userInfo.Id) - // Update ReferCode - err = db.Where("id = ?", userInfo.Id).Update("refer_code", userInfo.ReferCode).Error - if err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update refer code failed: %v", err.Error()) - } - authMethod := &user.AuthMethods{ - UserId: userInfo.Id, - AuthType: method, - AuthIdentifier: openid, - Verified: true, - } - if err = db.Create(authMethod).Error; err != nil { - l.Errorw("error create auth method: %v", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create auth method failed: %v", err.Error()) - } - if email != "" { - authMethod = &user.AuthMethods{ - UserId: userInfo.Id, - AuthType: "email", - AuthIdentifier: email, - Verified: true, - } - if err := db.Create(authMethod).Error; err != nil { - l.Errorw("error create auth method: %v", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create auth method failed: %v", err.Error()) - } - } - if l.svcCtx.Config.Register.EnableTrial { - // Active trial - if err = l.activeTrial(userInfo.Id); err != nil { - return err - } - } - return nil - }) - return userInfo, err -} +func (l *OAuthLoginGetTokenLogic) activeTrial(uid int64, requestID string) error { + l.Debugw("fetching trial subscription template", + logger.Field("request_id", requestID), + logger.Field("user_id", uid), + logger.Field("trial_subscribe_id", l.svcCtx.Config.Register.TrialSubscribe), + ) -func (l *OAuthLoginGetTokenLogic) activeTrial(uid int64) 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("request_id", requestID), + logger.Field("user_id", uid), + 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", uid)) + subscribeUUID := uuidx.NewUUID().String() + + l.Debugw("creating trial subscription", + logger.Field("request_id", requestID), + logger.Field("user_id", uid), + logger.Field("subscribe_id", sub.Id), + logger.Field("start_time", startTime), + logger.Field("expire_time", expireTime), + logger.Field("traffic", sub.Traffic), + logger.Field("token", subscribeToken), + logger.Field("uuid", subscribeUUID), + ) + userSub := &user.Subscribe{ Id: 0, UserId: uid, OrderId: 0, SubscribeId: sub.Id, - StartTime: time.Now(), - ExpireTime: tool.AddTime(l.svcCtx.Config.Register.TrialTimeUnit, l.svcCtx.Config.Register.TrialTime, time.Now()), + StartTime: startTime, + ExpireTime: expireTime, Traffic: sub.Traffic, Download: 0, Upload: 0, - Token: uuidx.SubscribeToken(fmt.Sprintf("Trial-%v", uid)), - UUID: uuidx.NewUUID().String(), + Token: subscribeToken, + UUID: subscribeUUID, Status: 1, } - return l.svcCtx.UserModel.InsertSubscribe(l.ctx, userSub) + + if err := l.svcCtx.UserModel.InsertSubscribe(l.ctx, userSub); err != nil { + l.Errorw("failed to insert trial subscription", + logger.Field("request_id", requestID), + logger.Field("user_id", uid), + logger.Field("error", err.Error()), + ) + return err + } + + l.Infow("trial subscription activated successfully", + logger.Field("request_id", requestID), + logger.Field("user_id", uid), + logger.Field("subscribe_id", sub.Id), + logger.Field("expire_time", expireTime), + logger.Field("traffic", sub.Traffic), + ) + return nil } diff --git a/internal/logic/auth/oauth/oAuthLoginLogic.go b/internal/logic/auth/oauth/oAuthLoginLogic.go index 5d2e6a4..063ed1d 100644 --- a/internal/logic/auth/oauth/oAuthLoginLogic.go +++ b/internal/logic/auth/oauth/oAuthLoginLogic.go @@ -6,14 +6,14 @@ import ( "fmt" "time" - "github.com/perfect-panel/ppanel-server/internal/model/auth" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/oauth/google" - "github.com/perfect-panel/ppanel-server/pkg/oauth/telegram" - "github.com/perfect-panel/ppanel-server/pkg/random" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/auth" + "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/oauth/google" + "github.com/perfect-panel/server/pkg/oauth/telegram" + "github.com/perfect-panel/server/pkg/random" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "golang.org/x/oauth2" ) diff --git a/internal/logic/auth/resetPasswordLogic.go b/internal/logic/auth/resetPasswordLogic.go index 2ea5bc7..c4e2ece 100644 --- a/internal/logic/auth/resetPasswordLogic.go +++ b/internal/logic/auth/resetPasswordLogic.go @@ -6,20 +6,21 @@ import ( "fmt" "time" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/jwt" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/pkg/jwt" + "github.com/perfect-panel/server/pkg/uuidx" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/config" + "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" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" ) type ResetPasswordLogic struct { @@ -43,13 +44,26 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res defer func() { if userInfo.Id != 0 && loginStatus { - if err := l.svcCtx.UserModel.InsertLoginLog(l.ctx, &user.LoginLog{ - UserId: userInfo.Id, + loginLog := log.Login{ + Method: "email", LoginIP: req.IP, UserAgent: req.UserAgent, - Success: &loginStatus, + Success: loginStatus, + Timestamp: time.Now().UnixMilli(), + } + content, _ := loginLog.Marshal() + if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{ + Id: 0, + Type: log.TypeLogin.Uint8(), + Date: time.Now().Format("2006-01-02"), + ObjectID: userInfo.Id, + Content: string(content), }); err != nil { - l.Logger.Error("[ResetPassword] insert login log error", logger.Field("error", err.Error())) + l.Errorw("failed to insert login log", + logger.Field("user_id", userInfo.Id), + logger.Field("ip", req.IP), + logger.Field("error", err.Error()), + ) } } }() @@ -93,6 +107,22 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res if err := l.svcCtx.UserModel.Update(l.ctx, userInfo); err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update user info failed: %v", err.Error()) } + + // Bind device to user if identifier is provided + if req.Identifier != "" { + bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx) + if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil { + l.Errorw("failed to bind device to user", + logger.Field("user_id", userInfo.Id), + logger.Field("identifier", req.Identifier), + logger.Field("error", err.Error()), + ) + // Don't fail register if device binding fails, just log the error + } + } + if l.ctx.Value(constant.LoginType) != nil { + req.LoginType = l.ctx.Value(constant.LoginType).(string) + } // Generate session id sessionId := uuidx.NewUUID().String() // Generate token @@ -102,6 +132,7 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res l.svcCtx.Config.JwtAuth.AccessExpire, jwt.WithOption("UserId", userInfo.Id), jwt.WithOption("SessionId", sessionId), + jwt.WithOption("LoginType", req.LoginType), ) if err != nil { l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) diff --git a/internal/logic/auth/telephoneLoginLogic.go b/internal/logic/auth/telephoneLoginLogic.go index 4f9dfc3..737157f 100644 --- a/internal/logic/auth/telephoneLoginLogic.go +++ b/internal/logic/auth/telephoneLoginLogic.go @@ -7,18 +7,19 @@ import ( "net/http" "time" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/logic/common" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/jwt" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/phone" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/logic/common" + "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/jwt" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/phone" + "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" ) @@ -51,13 +52,26 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r // Record login status defer func(svcCtx *svc.ServiceContext) { if userInfo.Id != 0 { - if err := svcCtx.UserModel.InsertLoginLog(l.ctx, &user.LoginLog{ - UserId: userInfo.Id, + loginLog := log.Login{ + Method: "mobile", LoginIP: ip, UserAgent: r.UserAgent(), - Success: &loginStatus, + Success: loginStatus, + Timestamp: time.Now().UnixMilli(), + } + content, _ := loginLog.Marshal() + if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{ + Id: 0, + Type: log.TypeLogin.Uint8(), + Date: time.Now().Format("2006-01-02"), + ObjectID: userInfo.Id, + Content: string(content), }); err != nil { - l.Logger.Error("[UserLogin] insert login log error", logger.Field("error", err.Error())) + l.Errorw("failed to insert login log", + logger.Field("user_id", userInfo.Id), + logger.Field("ip", req.IP), + logger.Field("error", err.Error()), + ) } } }(l.svcCtx) @@ -110,6 +124,23 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r l.svcCtx.Redis.Del(l.ctx, cacheKey) } + // Bind device to user if identifier is provided + if req.Identifier != "" { + bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx) + if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil { + l.Errorw("failed to bind device to user", + logger.Field("user_id", userInfo.Id), + logger.Field("identifier", req.Identifier), + logger.Field("error", err.Error()), + ) + // Don't fail login if device binding fails, just log the error + } + } + + if l.ctx.Value(constant.LoginType) != nil { + req.LoginType = l.ctx.Value(constant.LoginType).(string) + } + // Generate session id sessionId := uuidx.NewUUID().String() // Generate token @@ -119,6 +150,7 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r l.svcCtx.Config.JwtAuth.AccessExpire, jwt.WithOption("UserId", userInfo.Id), jwt.WithOption("SessionId", sessionId), + jwt.WithOption("LoginType", req.LoginType), ) if err != nil { l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) diff --git a/internal/logic/auth/telephoneResetPasswordLogic.go b/internal/logic/auth/telephoneResetPasswordLogic.go index 51ba9d7..f119c55 100644 --- a/internal/logic/auth/telephoneResetPasswordLogic.go +++ b/internal/logic/auth/telephoneResetPasswordLogic.go @@ -5,16 +5,17 @@ import ( "fmt" "time" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/jwt" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/phone" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/constant" + "github.com/perfect-panel/server/pkg/jwt" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/phone" + "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" ) @@ -82,6 +83,21 @@ func (l *TelephoneResetPasswordLogic) TelephoneResetPassword(req *types.Telephon return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "update user password failed: %v", err.Error()) } + // Bind device to user if identifier is provided + if req.Identifier != "" { + bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx) + if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil { + l.Errorw("failed to bind device to user", + logger.Field("user_id", userInfo.Id), + logger.Field("identifier", req.Identifier), + logger.Field("error", err.Error()), + ) + // Don't fail register if device binding fails, just log the error + } + } + if l.ctx.Value(constant.LoginType) != nil { + req.LoginType = l.ctx.Value(constant.LoginType).(string) + } // Generate session id sessionId := uuidx.NewUUID().String() // Generate token @@ -91,6 +107,7 @@ func (l *TelephoneResetPasswordLogic) TelephoneResetPassword(req *types.Telephon l.svcCtx.Config.JwtAuth.AccessExpire, jwt.WithOption("UserId", userInfo.Id), jwt.WithOption("SessionId", sessionId), + jwt.WithOption("LoginType", req.LoginType), ) if err != nil { l.Errorw("[UserLogin] token generate error", logger.Field("error", err.Error())) @@ -100,6 +117,31 @@ func (l *TelephoneResetPasswordLogic) TelephoneResetPassword(req *types.Telephon if err = l.svcCtx.Redis.Set(l.ctx, sessionIdCacheKey, userInfo.Id, time.Duration(l.svcCtx.Config.JwtAuth.AccessExpire)*time.Second).Err(); err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "set session id error: %v", err.Error()) } + defer func() { + if token != "" && userInfo.Id != 0 { + loginLog := log.Login{ + Method: "mobile", + LoginIP: req.IP, + UserAgent: req.UserAgent, + Success: token != "", + Timestamp: time.Now().UnixMilli(), + } + content, _ := loginLog.Marshal() + if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{ + Id: 0, + 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()), + ) + } + } + }() return &types.LoginResponse{ Token: token, }, nil diff --git a/internal/logic/auth/telephoneUserRegisterLogic.go b/internal/logic/auth/telephoneUserRegisterLogic.go index 24322b9..aa2dde9 100644 --- a/internal/logic/auth/telephoneUserRegisterLogic.go +++ b/internal/logic/auth/telephoneUserRegisterLogic.go @@ -6,18 +6,19 @@ import ( "fmt" "time" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/jwt" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/phone" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/jwt" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/phone" + "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" ) @@ -105,7 +106,8 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR // Generate password pwd := tool.EncodePassWord(req.Password) userInfo := &user.User{ - Password: pwd, + Password: pwd, + OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase, AuthMethods: []user.AuthMethods{ { AuthType: "mobile", @@ -136,6 +138,22 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR } return nil }) + + // Bind device to user if identifier is provided + if req.Identifier != "" { + bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx) + if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil { + l.Errorw("failed to bind device to user", + logger.Field("user_id", userInfo.Id), + logger.Field("identifier", req.Identifier), + logger.Field("error", err.Error()), + ) + // Don't fail register if device binding fails, just log the error + } + } + if l.ctx.Value(constant.LoginType) != nil { + req.LoginType = l.ctx.Value(constant.LoginType).(string) + } // Generate session id sessionId := uuidx.NewUUID().String() // Generate token @@ -145,6 +163,7 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR l.svcCtx.Config.JwtAuth.AccessExpire, jwt.WithOption("UserId", userInfo.Id), jwt.WithOption("SessionId", sessionId), + jwt.WithOption("LoginType", req.LoginType), ) if err != nil { l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) @@ -154,6 +173,53 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR if err = l.svcCtx.Redis.Set(l.ctx, sessionIdCacheKey, userInfo.Id, time.Duration(l.svcCtx.Config.JwtAuth.AccessExpire)*time.Second).Err(); err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "set session id error: %v", err.Error()) } + + defer func() { + if token != "" && userInfo.Id != 0 { + loginLog := log.Login{ + Method: "mobile", + LoginIP: req.IP, + UserAgent: req.UserAgent, + Success: token != "", + Timestamp: time.Now().UnixMilli(), + } + content, _ := loginLog.Marshal() + if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{ + Id: 0, + 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()), + ) + } + + // Register log + registerLog := log.Register{ + AuthMethod: "mobile", + Identifier: phoneNumber, + 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(), + ObjectID: userInfo.Id, + Date: time.Now().Format("2006-01-02"), + 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())) + } + } + }() return &types.LoginResponse{ Token: token, }, nil diff --git a/internal/logic/auth/userLoginLogic.go b/internal/logic/auth/userLoginLogic.go index ebc8ae9..3062e63 100644 --- a/internal/logic/auth/userLoginLogic.go +++ b/internal/logic/auth/userLoginLogic.go @@ -5,19 +5,21 @@ import ( "fmt" "time" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "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/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/jwt" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/pkg/jwt" + "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" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" ) type UserLoginLogic struct { @@ -41,29 +43,59 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log // Record login status defer func(svcCtx *svc.ServiceContext) { if userInfo.Id != 0 { - if err := svcCtx.UserModel.InsertLoginLog(l.ctx, &user.LoginLog{ - UserId: userInfo.Id, + loginLog := log.Login{ + Method: "email", LoginIP: req.IP, UserAgent: req.UserAgent, - Success: &loginStatus, + 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.Logger.Error("[UserLogin] insert login log error", logger.Field("error", err.Error())) + l.Errorw("failed to insert login log", + logger.Field("user_id", userInfo.Id), + logger.Field("ip", req.IP), + logger.Field("error", err.Error()), + ) } } }(l.svcCtx) userInfo, err = l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email) + if err != nil { if errors.As(err, &gorm.ErrRecordNotFound) { - logger.WithContext(l.ctx).Error(err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "user email not exist: %v", req.Email) } + logger.WithContext(l.ctx).Error(err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query user info failed: %v", err.Error()) } + // Verify password if !tool.VerifyPassWord(req.Password, userInfo.Password) { return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password") } + + // Bind device to user if identifier is provided + if req.Identifier != "" { + bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx) + if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil { + l.Errorw("failed to bind device to user", + logger.Field("user_id", userInfo.Id), + logger.Field("identifier", req.Identifier), + logger.Field("error", err.Error()), + ) + // Don't fail login if device binding fails, just log the error + } + } + if l.ctx.Value(constant.LoginType) != nil { + req.LoginType = l.ctx.Value(constant.LoginType).(string) + } // Generate session id sessionId := uuidx.NewUUID().String() // Generate token @@ -73,6 +105,7 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log l.svcCtx.Config.JwtAuth.AccessExpire, jwt.WithOption("UserId", userInfo.Id), jwt.WithOption("SessionId", sessionId), + jwt.WithOption("LoginType", req.LoginType), ) if err != nil { l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) diff --git a/internal/logic/auth/userRegisterLogic.go b/internal/logic/auth/userRegisterLogic.go index b03c94d..0b9da43 100644 --- a/internal/logic/auth/userRegisterLogic.go +++ b/internal/logic/auth/userRegisterLogic.go @@ -6,17 +6,18 @@ import ( "fmt" "time" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/logic/common" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/jwt" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/logic/common" + "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/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" ) @@ -88,7 +89,8 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp * // Generate password pwd := tool.EncodePassWord(req.Password) userInfo := &user.User{ - Password: pwd, + Password: pwd, + OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase, } if referer != nil { userInfo.RefererId = referer.Id @@ -123,6 +125,21 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp * } return nil }) + // Bind device to user if identifier is provided + if req.Identifier != "" { + bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx) + if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil { + l.Errorw("failed to bind device to user", + logger.Field("user_id", userInfo.Id), + logger.Field("identifier", req.Identifier), + logger.Field("error", err.Error()), + ) + // Don't fail register if device binding fails, just log the error + } + } + if l.ctx.Value(constant.LoginType) != nil { + req.LoginType = l.ctx.Value(constant.LoginType).(string) + } // Generate session id sessionId := uuidx.NewUUID().String() // Generate token @@ -132,6 +149,7 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp * l.svcCtx.Config.JwtAuth.AccessExpire, jwt.WithOption("UserId", userInfo.Id), jwt.WithOption("SessionId", sessionId), + jwt.WithOption("LoginType", req.LoginType), ) if err != nil { l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) @@ -145,13 +163,47 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp * loginStatus := true defer func() { if token != "" && userInfo.Id != 0 { - if err := l.svcCtx.UserModel.InsertLoginLog(l.ctx, &user.LoginLog{ - UserId: userInfo.Id, + loginLog := log.Login{ + Method: "email", LoginIP: req.IP, UserAgent: req.UserAgent, - Success: &loginStatus, + Success: loginStatus, + Timestamp: time.Now().UnixMilli(), + } + content, _ := loginLog.Marshal() + if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{ + Id: 0, + Type: log.TypeLogin.Uint8(), + Date: time.Now().Format("2006-01-02"), + ObjectID: userInfo.Id, + Content: string(content), }); err != nil { - l.Logger.Error("[UserRegister] insert login log error", logger.Field("error", err.Error())) + l.Errorw("failed to insert login log", + logger.Field("user_id", userInfo.Id), + logger.Field("ip", req.IP), + logger.Field("error", err.Error()), + ) + } + + // Register log + registerLog := log.Register{ + AuthMethod: "email", + Identifier: req.Email, + 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(), + ObjectID: userInfo.Id, + Date: time.Now().Format("2006-01-02"), + 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())) } } }() diff --git a/internal/logic/common/checkverificationcodelogic.go b/internal/logic/common/checkverificationcodelogic.go index 4aeb85b..1cc5812 100644 --- a/internal/logic/common/checkverificationcodelogic.go +++ b/internal/logic/common/checkverificationcodelogic.go @@ -5,14 +5,14 @@ import ( "encoding/json" "fmt" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/authmethod" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/phone" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/authmethod" + "github.com/perfect-panel/server/pkg/constant" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/phone" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) diff --git a/internal/logic/common/getAdsLogic.go b/internal/logic/common/getAdsLogic.go index 124d935..418be17 100644 --- a/internal/logic/common/getAdsLogic.go +++ b/internal/logic/common/getAdsLogic.go @@ -3,11 +3,11 @@ package common import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/ads" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/internal/model/ads" + "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" ) type GetAdsLogic struct { diff --git a/internal/logic/common/getApplicationLogic.go b/internal/logic/common/getApplicationLogic.go deleted file mode 100644 index 4477b3b..0000000 --- a/internal/logic/common/getApplicationLogic.go +++ /dev/null @@ -1,136 +0,0 @@ -package common - -import ( - "context" - "strings" - - "github.com/perfect-panel/ppanel-server/internal/model/application" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -type GetApplicationLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get Tos Content -func NewGetApplicationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetApplicationLogic { - return &GetApplicationLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *GetApplicationLogic) GetApplication() (resp *types.GetAppcationResponse, err error) { - resp = &types.GetAppcationResponse{} - - cfg, err := l.svcCtx.ApplicationModel.FindOneConfig(l.ctx, 1) - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - l.Logger.Error("[GetAppInfo] FindOneAppConfig error: ", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetAppInfo FindOneAppConfig error: %v", err.Error()) - } - if err != nil { - resp.Config = types.ApplicationConfig{} - } else { - resp.Config = types.ApplicationConfig{ - AppId: cfg.AppId, - EncryptionKey: cfg.EncryptionKey, - EncryptionMethod: cfg.EncryptionMethod, - Domains: strings.Split(cfg.Domains, ";"), - StartupPicture: cfg.StartupPicture, - StartupPictureSkipTime: cfg.StartupPictureSkipTime, - } - } - - var applications []*application.Application - err = l.svcCtx.ApplicationModel.Transaction(l.ctx, func(tx *gorm.DB) (err error) { - return tx.Model(applications).Preload("ApplicationVersions").Find(&applications).Error - }) - if err != nil { - l.Errorw("[QueryApplicationConfig] get application error: ", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get application error: %v", err.Error()) - } - - if len(applications) == 0 { - return resp, nil - } - - for _, app := range applications { - applicationResponse := types.ApplicationResponseInfo{ - Id: app.Id, - Name: app.Name, - Icon: app.Icon, - Description: app.Description, - SubscribeType: app.SubscribeType, - } - applicationVersions := app.ApplicationVersions - if len(applicationVersions) != 0 { - for _, applicationVersion := range applicationVersions { - /*if !applicationVersion.IsDefault { - continue - }*/ - switch applicationVersion.Platform { - case "ios": - applicationResponse.Platform.IOS = append(applicationResponse.Platform.IOS, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "macos": - applicationResponse.Platform.MacOS = append(applicationResponse.Platform.MacOS, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "linux": - applicationResponse.Platform.Linux = append(applicationResponse.Platform.Linux, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "android": - applicationResponse.Platform.Android = append(applicationResponse.Platform.Android, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "windows": - applicationResponse.Platform.Windows = append(applicationResponse.Platform.Windows, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "harmony": - applicationResponse.Platform.Harmony = append(applicationResponse.Platform.Harmony, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - } - } - } - resp.Applications = append(resp.Applications, applicationResponse) - } - - return -} diff --git a/internal/logic/common/getClientLogic.go b/internal/logic/common/getClientLogic.go new file mode 100644 index 0000000..7938de1 --- /dev/null +++ b/internal/logic/common/getClientLogic.go @@ -0,0 +1,56 @@ +package common + +import ( + "context" + "encoding/json" + + "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 GetClientLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// Get Client +func NewGetClientLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetClientLogic { + return &GetClientLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetClientLogic) GetClient() (resp *types.GetSubscribeClientResponse, err error) { + data, err := l.svcCtx.ClientModel.List(l.ctx) + if err != nil { + l.Errorf("Failed to get subscribe application list: %v", err) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Failed to get subscribe application list") + } + var list []types.SubscribeClient + for _, item := range data { + var temp types.DownloadLink + if item.DownloadLink != "" { + _ = json.Unmarshal([]byte(item.DownloadLink), &temp) + } + list = append(list, types.SubscribeClient{ + Id: item.Id, + Name: item.Name, + Description: item.Description, + Icon: item.Icon, + Scheme: item.Scheme, + IsDefault: item.IsDefault, + DownloadLink: temp, + }) + } + resp = &types.GetSubscribeClientResponse{ + Total: int64(len(list)), + List: list, + } + return +} diff --git a/internal/logic/common/getGlobalConfigLogic.go b/internal/logic/common/getGlobalConfigLogic.go index b43d3ab..61b2c1e 100644 --- a/internal/logic/common/getGlobalConfigLogic.go +++ b/internal/logic/common/getGlobalConfigLogic.go @@ -2,12 +2,13 @@ package common import ( "context" + "encoding/json" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) @@ -67,6 +68,10 @@ func (l *GetGlobalConfigLogic) GetGlobalConfig() (resp *types.GetGlobalConfigRes for _, method := range authMethods { if *method.Enabled { methods = append(methods, method.Method) + if method.Method == "device" { + _ = json.Unmarshal([]byte(method.Config), &resp.Auth.Device) + resp.Auth.Device.Enable = true + } } } resp.OAuthMethods = methods diff --git a/internal/logic/common/getPrivacyPolicyLogic.go b/internal/logic/common/getPrivacyPolicyLogic.go index ce6145f..c5bdceb 100644 --- a/internal/logic/common/getPrivacyPolicyLogic.go +++ b/internal/logic/common/getPrivacyPolicyLogic.go @@ -3,11 +3,11 @@ package common import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/common/getStatLogic.go b/internal/logic/common/getStatLogic.go index 9a4d604..df14af0 100644 --- a/internal/logic/common/getStatLogic.go +++ b/internal/logic/common/getStatLogic.go @@ -10,13 +10,13 @@ import ( "strings" "time" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/server" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/server" + "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/xerr" "github.com/pkg/errors" ) diff --git a/internal/logic/common/getSubscriptionLogic.go b/internal/logic/common/getSubscriptionLogic.go deleted file mode 100644 index bd5677e..0000000 --- a/internal/logic/common/getSubscriptionLogic.go +++ /dev/null @@ -1,41 +0,0 @@ -package common - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" -) - -type GetSubscriptionLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get Subscription -func NewGetSubscriptionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetSubscriptionLogic { - return &GetSubscriptionLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *GetSubscriptionLogic) GetSubscription() (resp *types.GetSubscriptionResponse, err error) { - resp = &types.GetSubscriptionResponse{ - List: make([]types.Subscribe, 0), - } - // Get the subscription list - data, err := l.svcCtx.SubscribeModel.QuerySubscribeListByShow(l.ctx) - if err != nil { - l.Errorw("[Site GetSubscription]", logger.Field("err", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get subscription list error: %v", err.Error()) - } - tool.DeepCopy(&resp.List, data) - return -} diff --git a/internal/logic/common/getTosLogic.go b/internal/logic/common/getTosLogic.go index 8884bc4..4297d75 100644 --- a/internal/logic/common/getTosLogic.go +++ b/internal/logic/common/getTosLogic.go @@ -3,11 +3,11 @@ package common import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/common/sendEmailCodeLogic.go b/internal/logic/common/sendEmailCodeLogic.go index 467f977..c538d72 100644 --- a/internal/logic/common/sendEmailCodeLogic.go +++ b/internal/logic/common/sendEmailCodeLogic.go @@ -1,26 +1,24 @@ package common import ( - "bytes" "context" "encoding/json" "fmt" - "text/template" "time" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/limit" - "github.com/perfect-panel/ppanel-server/pkg/random" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/pkg/constant" + "github.com/perfect-panel/server/pkg/limit" + "github.com/perfect-panel/server/pkg/random" "github.com/pkg/errors" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - queue "github.com/perfect-panel/ppanel-server/queue/types" + "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" + queue "github.com/perfect-panel/server/queue/types" ) type SendEmailCodeLogic struct { @@ -88,14 +86,16 @@ func (l *SendEmailCodeLogic) SendEmailCode(req *types.SendCodeRequest) (resp *ty var taskPayload queue.SendEmailPayload // Generate verification code code := random.Key(6, 0) + taskPayload.Type = queue.EmailTypeVerify taskPayload.Email = req.Email taskPayload.Subject = "Verification code" - content, err := l.initTemplate(req.Type, code) - if err != nil { - l.Logger.Error("[SendEmailCode]: InitTemplate Error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Failed to init template") + taskPayload.Content = map[string]interface{}{ + "Type": req.Type, + "SiteLogo": l.svcCtx.Config.Site.SiteLogo, + "SiteName": l.svcCtx.Config.Site.SiteName, + "Expire": 5, + "Code": code, } - taskPayload.Content = content // Save to Redis payload = CacheKeyPayload{ Code: code, @@ -134,23 +134,3 @@ func (l *SendEmailCodeLogic) SendEmailCode(req *types.SendCodeRequest) (resp *ty }, nil } } - -func (l *SendEmailCodeLogic) initTemplate(t uint8, code string) (string, error) { - data := VerifyTemplate{ - Type: t, - SiteLogo: l.svcCtx.Config.Site.SiteLogo, - SiteName: l.svcCtx.Config.Site.SiteName, - Expire: 5, - Code: code, - } - tpl, err := template.New("verify").Parse(l.svcCtx.Config.Email.VerifyEmailTemplate) - if err != nil { - return "", err - } - var result bytes.Buffer - err = tpl.Execute(&result, data) - if err != nil { - return "", err - } - return result.String(), nil -} diff --git a/internal/logic/common/sendSmsCodeLogic.go b/internal/logic/common/sendSmsCodeLogic.go index ada3f8d..7f5037a 100644 --- a/internal/logic/common/sendSmsCodeLogic.go +++ b/internal/logic/common/sendSmsCodeLogic.go @@ -7,16 +7,16 @@ import ( "time" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/limit" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/phone" - "github.com/perfect-panel/ppanel-server/pkg/random" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - queue "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/config" + "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/limit" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/phone" + "github.com/perfect-panel/server/pkg/random" + "github.com/perfect-panel/server/pkg/xerr" + queue "github.com/perfect-panel/server/queue/types" "github.com/pkg/errors" "gorm.io/gorm" ) diff --git a/internal/logic/notify/alipayNotifyLogic.go b/internal/logic/notify/alipayNotifyLogic.go index 125e41f..f0ce2aa 100644 --- a/internal/logic/notify/alipayNotifyLogic.go +++ b/internal/logic/notify/alipayNotifyLogic.go @@ -6,17 +6,17 @@ import ( "fmt" "net/http" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/payment" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/payment/alipay" - "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/model/payment" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/payment/alipay" + "github.com/perfect-panel/server/queue/types" ) type AlipayNotifyLogic struct { diff --git a/internal/logic/notify/ePayNotifyLogic.go b/internal/logic/notify/ePayNotifyLogic.go index 385ce1b..8def591 100644 --- a/internal/logic/notify/ePayNotifyLogic.go +++ b/internal/logic/notify/ePayNotifyLogic.go @@ -4,21 +4,21 @@ import ( "encoding/json" "net/url" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "github.com/gin-gonic/gin" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/payment" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/payment/epay" + "github.com/perfect-panel/server/internal/model/payment" + "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/payment/epay" - queueType "github.com/perfect-panel/ppanel-server/queue/types" + queueType "github.com/perfect-panel/server/queue/types" ) type EPayNotifyLogic struct { diff --git a/internal/logic/notify/stripeNotifyLogic.go b/internal/logic/notify/stripeNotifyLogic.go index a2c7e3a..a364339 100644 --- a/internal/logic/notify/stripeNotifyLogic.go +++ b/internal/logic/notify/stripeNotifyLogic.go @@ -6,17 +6,17 @@ import ( "io" "net/http" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/payment" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/payment/stripe" - "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/model/payment" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/payment/stripe" + "github.com/perfect-panel/server/queue/types" ) type StripeNotifyLogic struct { diff --git a/internal/logic/public/announcement/queryAnnouncementLogic.go b/internal/logic/public/announcement/queryAnnouncementLogic.go index 1f9b71a..e564ea9 100644 --- a/internal/logic/public/announcement/queryAnnouncementLogic.go +++ b/internal/logic/public/announcement/queryAnnouncementLogic.go @@ -3,14 +3,14 @@ package announcement import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/announcement" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/announcement" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type QueryAnnouncementLogic struct { diff --git a/internal/logic/public/document/queryDocumentDetailLogic.go b/internal/logic/public/document/queryDocumentDetailLogic.go index fbeae76..a5b90f4 100644 --- a/internal/logic/public/document/queryDocumentDetailLogic.go +++ b/internal/logic/public/document/queryDocumentDetailLogic.go @@ -3,11 +3,11 @@ package document import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/public/document/queryDocumentListLogic.go b/internal/logic/public/document/queryDocumentListLogic.go index bf386ad..9c82c8b 100644 --- a/internal/logic/public/document/queryDocumentListLogic.go +++ b/internal/logic/public/document/queryDocumentListLogic.go @@ -3,11 +3,11 @@ package document import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/public/order/calculateCoupon.go b/internal/logic/public/order/calculateCoupon.go index e9150cb..05f92c8 100644 --- a/internal/logic/public/order/calculateCoupon.go +++ b/internal/logic/public/order/calculateCoupon.go @@ -1,7 +1,7 @@ package order import ( - "github.com/perfect-panel/ppanel-server/internal/model/coupon" + "github.com/perfect-panel/server/internal/model/coupon" ) func calculateCoupon(amount int64, couponInfo *coupon.Coupon) int64 { diff --git a/internal/logic/public/order/calculateFee.go b/internal/logic/public/order/calculateFee.go index 432ad27..9c0b2b9 100644 --- a/internal/logic/public/order/calculateFee.go +++ b/internal/logic/public/order/calculateFee.go @@ -1,6 +1,6 @@ package order -import "github.com/perfect-panel/ppanel-server/internal/model/payment" +import "github.com/perfect-panel/server/internal/model/payment" func calculateFee(amount int64, config *payment.Payment) int64 { var fee float64 diff --git a/internal/logic/public/order/closeOrderLogic.go b/internal/logic/public/order/closeOrderLogic.go index 3ecfff3..ced53b8 100644 --- a/internal/logic/public/order/closeOrderLogic.go +++ b/internal/logic/public/order/closeOrderLogic.go @@ -3,17 +3,19 @@ package order import ( "context" "encoding/json" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/payment/payssion" - "github.com/perfect-panel/ppanel-server/pkg/payment/stripe" + "time" + + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/pkg/payment/stripe" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/model/payment" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/payment/alipay" + "github.com/perfect-panel/server/internal/model/order" + "github.com/perfect-panel/server/internal/model/payment" + "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/payment/alipay" ) type CloseOrderLogic struct { @@ -82,7 +84,7 @@ func (l *CloseOrderLogic) CloseOrder(req *types.CloseOrderRequest) error { return err } deduction := userInfo.GiftAmount + orderInfo.GiftAmount - err = tx.Model(&user.User{}).Where("id = ?", orderInfo.UserId).Update("deduction", deduction).Error + err = tx.Model(&user.User{}).Where("id = ?", orderInfo.UserId).Update("gift_amount", deduction).Error if err != nil { l.Errorw("[CloseOrder] Refund deduction amount failed", logger.Field("error", err.Error()), @@ -92,15 +94,25 @@ func (l *CloseOrderLogic) CloseOrder(req *types.CloseOrderRequest) error { return err } // Record the deduction refund log - giftAmountLog := &user.GiftAmountLog{ - UserId: orderInfo.UserId, - OrderNo: orderInfo.OrderNo, - Amount: orderInfo.GiftAmount, - Type: 1, - Balance: deduction, - Remark: "Order cancellation refund", + + giftLog := log.Gift{ + Type: log.GiftTypeIncrease, + OrderNo: orderInfo.OrderNo, + SubscribeId: 0, + Amount: orderInfo.GiftAmount, + Balance: deduction, + Remark: "Order cancellation refund", + Timestamp: time.Now().UnixMilli(), } - err = tx.Model(&user.GiftAmountLog{}).Create(giftAmountLog).Error + content, _ := giftLog.Marshal() + + err = tx.Model(&log.SystemLog{}).Create(&log.SystemLog{ + Id: 0, + Type: log.TypeGift.Uint8(), + Date: time.Now().Format(time.DateOnly), + ObjectID: userInfo.Id, + Content: string(content), + }).Error if err != nil { l.Errorw("[CloseOrder] Record cancellation refund log failed", logger.Field("error", err.Error()), @@ -134,8 +146,8 @@ func (l *CloseOrderLogic) confirmationPayment(order *order.Order) bool { if l.queryAlipay(paymentConfig, order.TradeNo) { return true } - case Payssion: - if l.queryPayssion(paymentConfig, order.TradeNo) { + case StripeAlipay: + if l.queryStripe(paymentConfig, order.TradeNo) { return true } case StripeWeChatPay: @@ -195,25 +207,3 @@ func (l *CloseOrderLogic) queryStripe(paymentConfig *payment.Payment, TradeNo st } return status } - -// queryPayssion Query Stripe payment status -// -//nolint:unused -func (l *CloseOrderLogic) queryPayssion(paymentConfig *payment.Payment, TradeNo string) bool { - l.Infof("[CloseOrder]1 Query Payssion called") - payssionConfig := payment.PayssionConfig{} - if err := json.Unmarshal([]byte(paymentConfig.Config), &payssionConfig); err != nil { - l.Errorw("[CloseOrder] Unmarshal error", logger.Field("error", err.Error())) - return false - } - l.Infof("[CloseOrder]2 Query Payssion called") - client := payssion.NewClient(payssionConfig.ApiKey, payssionConfig.SecretKey, payssionConfig.PmId, payssionConfig.Currency, payssionConfig.QueryUrl, payssionConfig.CreateUrl) - // create payment - result, err := client.QueryOrder(TradeNo) - if err != nil { - l.Errorw("[CloseOrder] Query order status failed", logger.Field("error", err.Error()), logger.Field("TradeNo", TradeNo)) - return false - } - l.Infof("[CloseOrder]3 Query Payssion called") - return result.Transaction.State == "completed" -} diff --git a/internal/logic/public/order/constant.go b/internal/logic/public/order/constant.go index 44bccd8..ca1c44a 100644 --- a/internal/logic/public/order/constant.go +++ b/internal/logic/public/order/constant.go @@ -2,7 +2,6 @@ package order const ( Epay = "epay" - Payssion = "Payssion" AlipayF2f = "alipay_f2f" StripeAlipay = "stripe_alipay" StripeWeChatPay = "stripe_wechat_pay" diff --git a/internal/logic/public/order/getDiscount.go b/internal/logic/public/order/getDiscount.go index 4d896f9..34c16a9 100644 --- a/internal/logic/public/order/getDiscount.go +++ b/internal/logic/public/order/getDiscount.go @@ -1,6 +1,6 @@ package order -import "github.com/perfect-panel/ppanel-server/internal/types" +import "github.com/perfect-panel/server/internal/types" func getDiscount(discounts []types.SubscribeDiscount, inputMonths int64) float64 { var finalDiscount int64 = 100 @@ -10,5 +10,6 @@ func getDiscount(discounts []types.SubscribeDiscount, inputMonths int64) float64 finalDiscount = discount.Discount } } + return float64(finalDiscount) / float64(100) } diff --git a/internal/logic/public/order/preCreateOrderLogic.go b/internal/logic/public/order/preCreateOrderLogic.go index 2989cde..4e16715 100644 --- a/internal/logic/public/order/preCreateOrderLogic.go +++ b/internal/logic/public/order/preCreateOrderLogic.go @@ -4,13 +4,16 @@ import ( "context" "encoding/json" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/internal/model/order" + "github.com/perfect-panel/server/pkg/tool" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/xerr" "github.com/pkg/errors" "gorm.io/gorm" ) @@ -21,7 +24,8 @@ type PreCreateOrderLogic struct { svcCtx *svc.ServiceContext } -// Pre create order +// NewPreCreateOrderLogic creates a new pre-create order logic instance for order preview operations. +// It initializes the logger with context and sets up the service context for database operations. func NewPreCreateOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PreCreateOrderLogic { return &PreCreateOrderLogic{ Logger: logger.WithContext(ctx), @@ -30,12 +34,21 @@ func NewPreCreateOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Pr } } +// PreCreateOrder calculates order pricing preview including discounts, coupons, gift amounts, and fees +// without actually creating an order. It validates subscription plans, coupons, and payment methods +// to provide accurate pricing information for the frontend order preview. func (l *PreCreateOrderLogic) PreCreateOrder(req *types.PurchaseOrderRequest) (resp *types.PreOrderResponse, 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 req.Quantity <= 0 { + l.Debugf("[PreCreateOrder] Quantity is less than or equal to 0, setting to 1") + req.Quantity = 1 + } + // find subscribe plan sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, req.SubscribeId) if err != nil { @@ -49,9 +62,10 @@ func (l *PreCreateOrderLogic) PreCreateOrder(req *types.PurchaseOrderRequest) (r discount = getDiscount(dis, req.Quantity) } price := sub.UnitPrice * req.Quantity + amount := int64(float64(price) * discount) discountAmount := price - amount - var coupon int64 + var couponAmount int64 if req.Coupon != "" { couponInfo, err := l.svcCtx.CouponModel.FindOneByCode(l.ctx, req.Coupon) if err != nil { @@ -60,12 +74,30 @@ func (l *PreCreateOrderLogic) PreCreateOrder(req *types.PurchaseOrderRequest) (r } return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find coupon error: %v", err.Error()) } - if couponInfo.Count <= couponInfo.UsedCount { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponUsed), "coupon used") + if couponInfo.Count > 0 && couponInfo.Count <= couponInfo.UsedCount { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponAlreadyUsed), "coupon used") } - coupon = calculateCoupon(amount, couponInfo) + var count int64 + err = l.svcCtx.DB.Transaction(func(tx *gorm.DB) error { + return tx.Model(&order.Order{}).Where("user_id = ? and coupon = ?", u.Id, req.Coupon).Count(&count).Error + }) + + if err != nil { + l.Errorw("[PreCreateOrder] Database query error", logger.Field("error", err.Error()), logger.Field("user_id", u.Id), logger.Field("coupon", req.Coupon)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find coupon error: %v", err.Error()) + } + + if couponInfo.UserLimit > 0 && count >= couponInfo.UserLimit { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponInsufficientUsage), "coupon limit exceeded") + } + + couponSub := tool.StringToInt64Slice(couponInfo.Subscribe) + if len(couponSub) > 0 && !tool.Contains(couponSub, req.SubscribeId) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponNotApplicable), "coupon not match") + } + couponAmount = calculateCoupon(amount, couponInfo) } - amount -= coupon + amount -= couponAmount var deductionAmount int64 // Check user deduction amount @@ -82,7 +114,7 @@ func (l *PreCreateOrderLogic) PreCreateOrder(req *types.PurchaseOrderRequest) (r if req.Payment != 0 { payment, err := l.svcCtx.PaymentModel.FindOne(l.ctx, req.Payment) if err != nil { - l.Logger.Error("[PreCreateOrder] Database query error", logger.Field("error", err.Error()), logger.Field("payment", req.Payment)) + l.Errorw("[PreCreateOrder] Database query error", logger.Field("error", err.Error()), logger.Field("payment", req.Payment)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find payment method error: %v", err.Error()) } // Calculate the handling fee @@ -98,7 +130,7 @@ func (l *PreCreateOrderLogic) PreCreateOrder(req *types.PurchaseOrderRequest) (r Discount: discountAmount, GiftAmount: deductionAmount, Coupon: req.Coupon, - CouponDiscount: coupon, + CouponDiscount: couponAmount, FeeAmount: feeAmount, } return diff --git a/internal/logic/public/order/purchaseLogic.go b/internal/logic/public/order/purchaseLogic.go index a559f38..519a80a 100644 --- a/internal/logic/public/order/purchaseLogic.go +++ b/internal/logic/public/order/purchaseLogic.go @@ -5,20 +5,21 @@ import ( "encoding/json" "time" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/pkg/constant" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - queue "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/model/order" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" + queue "github.com/perfect-panel/server/queue/types" "github.com/pkg/errors" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type PurchaseLogic struct { @@ -31,7 +32,8 @@ const ( CloseOrderTimeMinutes = 15 ) -// NewPurchaseLogic purchase Subscription +// NewPurchaseLogic creates a new purchase logic instance for subscription purchase operations. +// It initializes the logger with context and sets up the service context for database operations. func NewPurchaseLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PurchaseLogic { return &PurchaseLogic{ Logger: logger.WithContext(ctx), @@ -40,6 +42,9 @@ func NewPurchaseLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Purchase } } +// Purchase processes new subscription purchase orders including validation, discount calculation, +// coupon processing, gift amount deduction, fee calculation, and order creation with database transaction. +// It handles the complete purchase workflow from user validation to order creation and task scheduling. func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.PurchaseOrderResponse, err error) { u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User) @@ -47,6 +52,12 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P logger.Error("current user is not found in context") return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access") } + + if req.Quantity <= 0 { + l.Debugf("[Purchase] Quantity is less than or equal to 0, setting to 1") + req.Quantity = 1 + } + // find user subscription userSub, err := l.svcCtx.UserModel.QueryUserSubscribe(l.ctx, u.Id) if err != nil { @@ -103,12 +114,24 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P } return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find coupon error: %v", err.Error()) } - if couponInfo.Count <= couponInfo.UsedCount { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponUsed), "coupon used") + if couponInfo.Count != 0 && couponInfo.Count <= couponInfo.UsedCount { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponInsufficientUsage), "coupon used") } couponSub := tool.StringToInt64Slice(couponInfo.Subscribe) if len(couponSub) > 0 && !tool.Contains(couponSub, req.SubscribeId) { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponNotMatch), "coupon not match") + return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponNotApplicable), "coupon not match") + } + var count int64 + err = l.svcCtx.DB.Transaction(func(tx *gorm.DB) error { + return tx.Model(&order.Order{}).Where("user_id = ? and coupon = ?", u.Id, req.Coupon).Count(&count).Error + }) + + if err != nil { + l.Errorw("[Purchase] Database query error", logger.Field("error", err.Error()), logger.Field("user_id", u.Id), logger.Field("coupon", req.Coupon)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find coupon error: %v", err.Error()) + } + if count >= couponInfo.UserLimit { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponInsufficientUsage), "coupon limit exceeded") } coupon = calculateCoupon(amount, couponInfo) } @@ -120,7 +143,7 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P if u.GiftAmount >= amount { deductionAmount = amount amount = 0 - u.GiftAmount -= amount + u.GiftAmount -= deductionAmount } else { deductionAmount = u.GiftAmount amount -= u.GiftAmount @@ -130,13 +153,14 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P // find payment method payment, err := l.svcCtx.PaymentModel.FindOne(l.ctx, req.Payment) if err != nil { - l.Logger.Error("[Purchase] Database query error", logger.Field("error", err.Error()), logger.Field("payment", req.Payment)) + l.Errorw("[Purchase] Database query error", logger.Field("error", err.Error()), logger.Field("payment", req.Payment)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find payment method error: %v", err.Error()) } var feeAmount int64 // Calculate the handling fee if amount > 0 { feeAmount = calculateFee(amount, payment) + amount += feeAmount } // query user is new purchase or renewal isNew, err := l.svcCtx.OrderModel.IsUserEligibleForNewOrder(l.ctx, u.Id) @@ -168,23 +192,31 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P // update user deduction && Pre deduction ,Return after canceling the order if orderInfo.GiftAmount > 0 { // update user deduction && Pre deduction ,Return after canceling the order - if e := l.svcCtx.UserModel.Update(l.ctx, u, db); err != nil { - l.Errorw("[Purchase] Database update error", logger.Field("error", err.Error()), logger.Field("user", u)) + if e := l.svcCtx.UserModel.Update(l.ctx, u, db); e != nil { + l.Errorw("[Purchase] Database update error", logger.Field("error", e.Error()), logger.Field("user", u)) return e } // create deduction record - giftAmountLog := user.GiftAmountLog{ - UserId: orderInfo.UserId, - OrderNo: orderInfo.OrderNo, - Amount: orderInfo.GiftAmount, - Type: 2, - Balance: u.GiftAmount, - Remark: "Purchase order deduction", + giftLog := log.Gift{ + Type: log.GiftTypeReduce, + OrderNo: orderInfo.OrderNo, + SubscribeId: 0, + Amount: orderInfo.GiftAmount, + Balance: u.GiftAmount, + Remark: "Purchase order deduction", + Timestamp: time.Now().UnixMilli(), } - if e := db.Model(&user.GiftAmountLog{}).Create(&giftAmountLog).Error; e != nil { + content, _ := giftLog.Marshal() + + if e := db.Model(&log.SystemLog{}).Create(&log.SystemLog{ + Type: log.TypeGift.Uint8(), + Date: time.Now().Format(time.DateOnly), + ObjectID: u.Id, + Content: string(content), + }).Error; e != nil { l.Errorw("[Purchase] Database insert error", - logger.Field("error", err.Error()), - logger.Field("deductionLog", giftAmountLog), + logger.Field("error", e.Error()), + logger.Field("deductionLog", giftLog), ) return e } @@ -201,14 +233,14 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P } val, err := json.Marshal(payload) if err != nil { - l.Errorw("[CreateOrder] Marshal payload error", logger.Field("error", err.Error()), logger.Field("payload", payload)) + l.Errorw("[Purchase] Marshal payload error", logger.Field("error", err.Error()), logger.Field("payload", payload)) } task := asynq.NewTask(queue.DeferCloseOrder, val, asynq.MaxRetry(3)) taskInfo, err := l.svcCtx.Queue.Enqueue(task, asynq.ProcessIn(CloseOrderTimeMinutes*time.Minute)) if err != nil { - l.Errorw("[CreateOrder] Enqueue task error", logger.Field("error", err.Error()), logger.Field("task", task)) + l.Errorw("[Purchase] Enqueue task error", logger.Field("error", err.Error()), logger.Field("task", task)) } else { - l.Infow("[CreateOrder] Enqueue task success", logger.Field("TaskID", taskInfo.ID)) + l.Infow("[Purchase] Enqueue task success", logger.Field("TaskID", taskInfo.ID)) } return &types.PurchaseOrderResponse{ diff --git a/internal/logic/public/order/queryOrderDetailLogic.go b/internal/logic/public/order/queryOrderDetailLogic.go index e34392b..21dd68d 100644 --- a/internal/logic/public/order/queryOrderDetailLogic.go +++ b/internal/logic/public/order/queryOrderDetailLogic.go @@ -3,11 +3,11 @@ package order import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/public/order/queryOrderListLogic.go b/internal/logic/public/order/queryOrderListLogic.go index 544b157..13deaa3 100644 --- a/internal/logic/public/order/queryOrderListLogic.go +++ b/internal/logic/public/order/queryOrderListLogic.go @@ -3,14 +3,14 @@ package order import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/public/order/rechargeLogic.go b/internal/logic/public/order/rechargeLogic.go index a42d3bf..04ff41c 100644 --- a/internal/logic/public/order/rechargeLogic.go +++ b/internal/logic/public/order/rechargeLogic.go @@ -5,17 +5,17 @@ import ( "encoding/json" "time" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/pkg/constant" + "github.com/perfect-panel/server/pkg/xerr" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - queue "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/model/order" + "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" + queue "github.com/perfect-panel/server/queue/types" "github.com/pkg/errors" ) diff --git a/internal/logic/public/order/renewalLogic.go b/internal/logic/public/order/renewalLogic.go index ad59f72..c78824f 100644 --- a/internal/logic/public/order/renewalLogic.go +++ b/internal/logic/public/order/renewalLogic.go @@ -5,19 +5,20 @@ import ( "encoding/json" "time" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/pkg/constant" "gorm.io/gorm" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - queue "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/model/order" + "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" + queue "github.com/perfect-panel/server/queue/types" "github.com/pkg/errors" ) @@ -27,7 +28,7 @@ type RenewalLogic struct { svcCtx *svc.ServiceContext } -// Renewal Subscription +// NewRenewalLogic creates a new renewal logic instance for subscription renewal operations func NewRenewalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RenewalLogic { return &RenewalLogic{ Logger: logger.WithContext(ctx), @@ -36,12 +37,19 @@ func NewRenewalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RenewalLo } } +// Renewal processes subscription renewal orders including discount calculation, +// coupon validation, gift amount deduction, fee calculation, and order creation func (l *RenewalLogic) Renewal(req *types.RenewalOrderRequest) (resp *types.RenewalOrderResponse, 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 req.Quantity <= 0 { + l.Debugf("[Renewal] Quantity is less than or equal to 0, setting to 1") + req.Quantity = 1 + } + orderNo := tool.GenerateTradeNo() // find user subscribe userSubscribe, err := l.svcCtx.UserModel.FindOneUserSubscribe(l.ctx, req.UserSubscribeID) @@ -76,15 +84,31 @@ func (l *RenewalLogic) Renewal(req *types.RenewalOrderRequest) (resp *types.Rene } return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find coupon error: %v", err.Error()) } - if couponInfo.Count <= couponInfo.UsedCount { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponUsed), "coupon used") + if couponInfo.Count != 0 && couponInfo.Count <= couponInfo.UsedCount { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponInsufficientUsage), "coupon used") + } + couponSub := tool.StringToInt64Slice(couponInfo.Subscribe) + + if len(couponSub) > 0 && !tool.Contains(couponSub, sub.Id) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponNotApplicable), "coupon not match") + } + var count int64 + err = l.svcCtx.DB.Transaction(func(tx *gorm.DB) error { + return tx.Model(&order.Order{}).Where("user_id = ? and coupon = ?", u.Id, req.Coupon).Count(&count).Error + }) + if err != nil { + l.Errorw("[Renewal] Database query error", logger.Field("error", err.Error()), logger.Field("user_id", u.Id), logger.Field("coupon", req.Coupon)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find coupon error: %v", err.Error()) + } + if count >= couponInfo.UserLimit { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponInsufficientUsage), "coupon limit exceeded") } coupon = calculateCoupon(amount, couponInfo) } payment, err := l.svcCtx.PaymentModel.FindOne(l.ctx, req.Payment) if err != nil { l.Errorw("[Renewal] Database query error", logger.Field("error", err.Error()), logger.Field("payment", req.Payment)) - return nil, errors.Wrapf(err, "find payment error: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find payment error: %v", err.Error()) } amount -= coupon @@ -93,8 +117,8 @@ func (l *RenewalLogic) Renewal(req *types.RenewalOrderRequest) (resp *types.Rene if u.GiftAmount > 0 { if u.GiftAmount >= amount { deductionAmount = amount + u.GiftAmount -= deductionAmount amount = 0 - u.GiftAmount -= amount } else { deductionAmount = u.GiftAmount amount -= u.GiftAmount @@ -136,20 +160,28 @@ func (l *RenewalLogic) Renewal(req *types.RenewalOrderRequest) (resp *types.Rene if orderInfo.GiftAmount > 0 { // update user deduction && Pre deduction ,Return after canceling the order if err := l.svcCtx.UserModel.Update(l.ctx, u, db); err != nil { - l.Errorw("[Purchase] Database update error", logger.Field("error", err.Error()), logger.Field("user", u)) + l.Errorw("[Renewal] Database update error", logger.Field("error", err.Error()), logger.Field("user", u)) return err } // create deduction record - deductionLog := user.GiftAmountLog{ - UserId: orderInfo.UserId, - OrderNo: orderInfo.OrderNo, - Amount: orderInfo.GiftAmount, - Type: 2, - Balance: u.GiftAmount, - Remark: "Renewal order deduction", + giftLog := log.Gift{ + Type: log.GiftTypeReduce, + OrderNo: orderInfo.OrderNo, + SubscribeId: 0, + Amount: orderInfo.GiftAmount, + Balance: u.GiftAmount, + Remark: "Renewal order deduction", + Timestamp: time.Now().UnixMilli(), } - if err := db.Model(&user.GiftAmountLog{}).Create(&deductionLog).Error; err != nil { - l.Errorw("[Renewal] Database insert error", logger.Field("error", err.Error()), logger.Field("deductionLog", deductionLog)) + content, _ := giftLog.Marshal() + + if err := db.Model(&log.SystemLog{}).Create(&log.SystemLog{ + Type: log.TypeGift.Uint8(), + Date: time.Now().Format(time.DateOnly), + ObjectID: u.Id, + Content: string(content), + }).Error; err != nil { + l.Errorw("[Renewal] Database insert error", logger.Field("error", err.Error()), logger.Field("deductionLog", giftLog)) return err } } diff --git a/internal/logic/public/order/resetTrafficLogic.go b/internal/logic/public/order/resetTrafficLogic.go index 2f3ecc1..1fc9b57 100644 --- a/internal/logic/public/order/resetTrafficLogic.go +++ b/internal/logic/public/order/resetTrafficLogic.go @@ -5,19 +5,20 @@ import ( "encoding/json" "time" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/pkg/constant" + "github.com/perfect-panel/server/pkg/xerr" "gorm.io/gorm" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - queue "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/model/order" + "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" + queue "github.com/perfect-panel/server/queue/types" "github.com/pkg/errors" ) @@ -104,16 +105,24 @@ func (l *ResetTrafficLogic) ResetTraffic(req *types.ResetTrafficOrderRequest) (r return err } // create deduction record - deductionLog := user.GiftAmountLog{ - UserId: orderInfo.UserId, - OrderNo: orderInfo.OrderNo, - Amount: orderInfo.GiftAmount, - Type: 2, - Balance: u.GiftAmount, - Remark: "ResetTraffic order deduction", + giftLog := log.Gift{ + Type: log.GiftTypeReduce, + OrderNo: orderInfo.OrderNo, + SubscribeId: 0, + Amount: orderInfo.GiftAmount, + Balance: u.GiftAmount, + Remark: "Renewal order deduction", + Timestamp: time.Now().UnixMilli(), } - if err := db.Model(&user.GiftAmountLog{}).Create(&deductionLog).Error; err != nil { - l.Errorw("[ResetTraffic] Database insert error", logger.Field("error", err.Error()), logger.Field("deductionLog", deductionLog)) + content, _ := giftLog.Marshal() + + if err = db.Model(&log.SystemLog{}).Create(&log.SystemLog{ + Type: log.TypeGift.Uint8(), + Date: time.Now().Format(time.DateOnly), + ObjectID: u.Id, + Content: string(content), + }).Error; err != nil { + l.Errorw("[ResetTraffic] Database insert error", logger.Field("error", err.Error()), logger.Field("deductionLog", content)) return err } } diff --git a/internal/logic/public/payment/getAvailablePaymentMethodsLogic.go b/internal/logic/public/payment/getAvailablePaymentMethodsLogic.go index 835f90d..b8c88cb 100644 --- a/internal/logic/public/payment/getAvailablePaymentMethodsLogic.go +++ b/internal/logic/public/payment/getAvailablePaymentMethodsLogic.go @@ -3,11 +3,11 @@ package payment import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/public/portal/getAvailablePaymentMethodsLogic.go b/internal/logic/public/portal/getAvailablePaymentMethodsLogic.go index 8e5faad..0e6bbc5 100644 --- a/internal/logic/public/portal/getAvailablePaymentMethodsLogic.go +++ b/internal/logic/public/portal/getAvailablePaymentMethodsLogic.go @@ -3,11 +3,11 @@ package portal import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/public/portal/getSubscriptionLogic.go b/internal/logic/public/portal/getSubscriptionLogic.go index df17f8a..51fbaa6 100644 --- a/internal/logic/public/portal/getSubscriptionLogic.go +++ b/internal/logic/public/portal/getSubscriptionLogic.go @@ -4,11 +4,12 @@ import ( "context" "encoding/json" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/subscribe" + "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" ) @@ -27,12 +28,18 @@ func NewGetSubscriptionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *G } } -func (l *GetSubscriptionLogic) GetSubscription() (resp *types.GetSubscriptionResponse, err error) { +func (l *GetSubscriptionLogic) GetSubscription(req *types.GetSubscriptionRequest) (resp *types.GetSubscriptionResponse, err error) { resp = &types.GetSubscriptionResponse{ List: make([]types.Subscribe, 0), } // Get the subscription list - data, err := l.svcCtx.SubscribeModel.QuerySubscribeListByShow(l.ctx) + _, data, err := l.svcCtx.SubscribeModel.FilterList(l.ctx, &subscribe.FilterParams{ + Page: 1, + Size: 9999, + Show: true, + Language: req.Language, + DefaultLanguage: true, + }) if err != nil { l.Errorw("[Site GetSubscription]", logger.Field("err", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get subscription list error: %v", err.Error()) diff --git a/internal/logic/public/portal/prePurchaseOrderLogic.go b/internal/logic/public/portal/prePurchaseOrderLogic.go index 3d51dbe..b2a99ba 100644 --- a/internal/logic/public/portal/prePurchaseOrderLogic.go +++ b/internal/logic/public/portal/prePurchaseOrderLogic.go @@ -4,10 +4,12 @@ import ( "context" "encoding/json" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/pkg/tool" + + "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" "gorm.io/gorm" ) @@ -52,9 +54,15 @@ func (l *PrePurchaseOrderLogic) PrePurchaseOrder(req *types.PrePurchaseOrderRequ } return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find coupon error: %v", err.Error()) } - if couponInfo.Count <= couponInfo.UsedCount { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponUsed), "coupon used") + if couponInfo.Count != 0 && couponInfo.Count <= couponInfo.UsedCount { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponInsufficientUsage), "coupon used") } + subs := tool.StringToInt64Slice(couponInfo.Subscribe) + + if len(subs) > 0 && !tool.Contains(subs, req.SubscribeId) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponNotApplicable), "coupon not match") + } + coupon = calculateCoupon(amount, couponInfo) } amount -= coupon diff --git a/internal/logic/public/portal/purchaseCheckoutLogic.go b/internal/logic/public/portal/purchaseCheckoutLogic.go index b991188..f72ed4f 100644 --- a/internal/logic/public/portal/purchaseCheckoutLogic.go +++ b/internal/logic/public/portal/purchaseCheckoutLogic.go @@ -3,39 +3,43 @@ package portal import ( "context" "encoding/json" - "github.com/perfect-panel/ppanel-server/pkg/payment/payssion" "strconv" + "time" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/pkg/constant" - paymentPlatform "github.com/perfect-panel/ppanel-server/pkg/payment" + paymentPlatform "github.com/perfect-panel/server/pkg/payment" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/user" - queueType "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/model/user" + queueType "github.com/perfect-panel/server/queue/types" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/model/payment" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/exchangeRate" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/payment/alipay" - "github.com/perfect-panel/ppanel-server/pkg/payment/epay" - "github.com/perfect-panel/ppanel-server/pkg/payment/stripe" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/order" + "github.com/perfect-panel/server/internal/model/payment" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/exchangeRate" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/payment/alipay" + "github.com/perfect-panel/server/pkg/payment/epay" + "github.com/perfect-panel/server/pkg/payment/stripe" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) +// PurchaseCheckoutLogic handles the checkout process for various payment methods +// including EPay, Stripe, Alipay F2F, and balance payments type PurchaseCheckoutLogic struct { logger.Logger ctx context.Context svcCtx *svc.ServiceContext } -// NewPurchaseCheckoutLogic Purchase Checkout +// NewPurchaseCheckoutLogic creates a new instance of PurchaseCheckoutLogic +// for handling purchase checkout operations across different payment platforms func NewPurchaseCheckoutLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PurchaseCheckoutLogic { return &PurchaseCheckoutLogic{ Logger: logger.WithContext(ctx), @@ -44,98 +48,116 @@ func NewPurchaseCheckoutLogic(ctx context.Context, svcCtx *svc.ServiceContext) * } } +// PurchaseCheckout processes the checkout for an order using the specified payment method +// It validates the order, retrieves payment configuration, and routes to the appropriate payment handler func (l *PurchaseCheckoutLogic) PurchaseCheckout(req *types.CheckoutOrderRequest) (resp *types.CheckoutOrderResponse, err error) { - // Find order + // Validate and retrieve order information orderInfo, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo) if err != nil { l.Logger.Error("[PurchaseCheckout] Find order failed", logger.Field("error", err.Error()), logger.Field("orderNo", req.OrderNo)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.OrderNotExist), "order not exist: %v", req.OrderNo) } + // Verify order is in pending payment status (status = 1) if orderInfo.Status != 1 { l.Logger.Error("[PurchaseCheckout] Order status error", logger.Field("status", orderInfo.Status)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.OrderStatusError), "order status error: %v", orderInfo.Status) } - // find payment method + // Retrieve payment method configuration paymentConfig, err := l.svcCtx.PaymentModel.FindOne(l.ctx, orderInfo.PaymentId) if err != nil { - l.Logger.Error("[Purchase] Database query error", logger.Field("error", err.Error()), logger.Field("payment", orderInfo.Method)) + l.Logger.Error("[PurchaseCheckout] Database query error", logger.Field("error", err.Error()), logger.Field("payment", orderInfo.Method)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find payment method error: %v", err.Error()) } + // Route to appropriate payment handler based on payment platform switch paymentPlatform.ParsePlatform(orderInfo.Method) { case paymentPlatform.EPay: + // Process EPay payment - generates payment URL for redirect url, err := l.epayPayment(paymentConfig, orderInfo, req.ReturnUrl) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "epayPayment error: %v", err.Error()) } resp = &types.CheckoutOrderResponse{ CheckoutUrl: url, - Type: "url", + Type: "url", // Client should redirect to URL } + case paymentPlatform.Stripe: + // Process Stripe payment - creates payment sheet for client-side processing stripePayment, err := l.stripePayment(paymentConfig.Config, orderInfo, "") if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "stripePayment error: %v", err.Error()) } resp = &types.CheckoutOrderResponse{ - Type: "stripe", + Type: "stripe", // Client should use Stripe SDK Stripe: stripePayment, } + case paymentPlatform.AlipayF2F: + // Process Alipay Face-to-Face payment - generates QR code url, err := l.alipayF2fPayment(paymentConfig, orderInfo) if err != nil { - l.Errorw("[CheckoutOrderLogic] alipayF2fPayment error", logger.Field("error", err.Error())) + l.Errorw("[PurchaseCheckout] alipayF2fPayment error", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "alipayF2fPayment error: %v", err.Error()) } resp = &types.CheckoutOrderResponse{ - Type: "qr", + Type: "qr", // Client should display QR code CheckoutUrl: url, } - case paymentPlatform.Payssion: - url, err := l.payssionPayment(paymentConfig, orderInfo, req.ReturnUrl) + + case paymentPlatform.CryptoSaaS: + // Process EPay payment - generates payment URL for redirect + url, err := l.CryptoSaaSPayment(paymentConfig, orderInfo, req.ReturnUrl) if err != nil { - l.Errorw("[CheckoutOrderLogic] payssionPayment error", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "paymentPayment error: %v", err.Error()) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "epayPayment error: %v", err.Error()) } resp = &types.CheckoutOrderResponse{ CheckoutUrl: url, - Type: "url", + Type: "url", // Client should redirect to URL } + case paymentPlatform.Balance: + // Process balance payment - validate user and process payment immediately if orderInfo.UserId == 0 { - l.Errorw("[CheckoutOrderLogic] user not found", logger.Field("userId", orderInfo.UserId)) + l.Errorw("[PurchaseCheckout] user not found", logger.Field("userId", orderInfo.UserId)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "user not found") } - // find user + + // Retrieve user information for balance validation userInfo, err := l.svcCtx.UserModel.FindOne(l.ctx, orderInfo.UserId) if err != nil { - l.Errorw("[CheckoutOrderLogic] FindOne User error", logger.Field("error", err.Error())) + l.Errorw("[PurchaseCheckout] FindOne User error", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindOne error: %s", err.Error()) } - // balance + // Process balance payment with gift amount priority logic if err = l.balancePayment(userInfo, orderInfo); err != nil { return nil, err } + resp = &types.CheckoutOrderResponse{ - Type: "balance", + Type: "balance", // Payment completed immediately } default: - l.Errorw("[CheckoutOrderLogic] payment method not found", logger.Field("method", orderInfo.Method)) + l.Errorw("[PurchaseCheckout] payment method not found", logger.Field("method", orderInfo.Method)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "payment method not found") } return } -// alipay f2f payment +// alipayF2fPayment processes Alipay Face-to-Face payment by generating a QR code +// It handles currency conversion and creates a pre-payment trade for QR code scanning func (l *PurchaseCheckoutLogic) alipayF2fPayment(pay *payment.Payment, info *order.Order) (string, error) { - f2FConfig := payment.AlipayF2FConfig{} - if err := json.Unmarshal([]byte(pay.Config), &f2FConfig); err != nil { - l.Errorw("[PurchaseCheckoutLogic] Unmarshal error", logger.Field("error", err.Error())) + // Parse Alipay F2F configuration from payment settings + f2FConfig := &payment.AlipayF2FConfig{} + if err := f2FConfig.Unmarshal([]byte(pay.Config)); err != nil { + l.Errorw("[PurchaseCheckout] Unmarshal Alipay config error", logger.Field("error", err.Error())) return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error()) } + + // Build notification URL for payment status callbacks notifyUrl := "" if pay.Domain != "" { notifyUrl = pay.Domain + "/v1/notify/" + pay.Platform + "/" + pay.Token @@ -146,6 +168,8 @@ func (l *PurchaseCheckoutLogic) alipayF2fPayment(pay *payment.Payment, info *ord } notifyUrl = "https://" + host + "/v1/notify/" + pay.Platform + "/" + pay.Token } + + // Initialize Alipay client with configuration client := alipay.NewClient(alipay.Config{ AppId: f2FConfig.AppId, PrivateKey: f2FConfig.PrivateKey, @@ -153,46 +177,54 @@ func (l *PurchaseCheckoutLogic) alipayF2fPayment(pay *payment.Payment, info *ord InvoiceName: f2FConfig.InvoiceName, NotifyURL: notifyUrl, }) - // Calculate the amount with exchange rate + + // Convert order amount to CNY using current exchange rate amount, err := l.queryExchangeRate("CNY", info.Amount) if err != nil { - l.Errorw("[CheckoutOrderLogic] queryExchangeRate error", logger.Field("error", err.Error())) + l.Errorw("[PurchaseCheckout] queryExchangeRate error", logger.Field("error", err.Error())) return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "queryExchangeRate error: %s", err.Error()) } - convertAmount := int64(amount * 100) - // create payment + convertAmount := int64(amount * 100) // Convert to cents for API + + // Create pre-payment trade and generate QR code QRCode, err := client.PreCreateTrade(l.ctx, alipay.Order{ OrderNo: info.OrderNo, Amount: convertAmount, }) if err != nil { - l.Errorw("[CheckoutOrderLogic] PreCreateTrade error", logger.Field("error", err.Error())) + l.Errorw("[PurchaseCheckout] PreCreateTrade error", logger.Field("error", err.Error())) return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "PreCreateTrade error: %s", err.Error()) } return QRCode, nil } -// Stripe Payment +// stripePayment processes Stripe payment by creating a payment sheet +// It supports various payment methods including WeChat Pay and Alipay through Stripe func (l *PurchaseCheckoutLogic) stripePayment(config string, info *order.Order, identifier string) (*types.StripePayment, error) { - // stripe WeChat pay or stripe alipay - stripeConfig := payment.StripeConfig{} - if err := json.Unmarshal([]byte(config), &stripeConfig); err != nil { - l.Errorw("[CheckoutOrderLogic] Unmarshal error", logger.Field("error", err.Error())) + // Parse Stripe configuration from payment settings + stripeConfig := &payment.StripeConfig{} + + if err := stripeConfig.Unmarshal([]byte(config)); err != nil { + l.Errorw("[PurchaseCheckout] Unmarshal Stripe config error", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error()) } + + // Initialize Stripe client with API credentials client := stripe.NewClient(stripe.Config{ SecretKey: stripeConfig.SecretKey, PublicKey: stripeConfig.PublicKey, WebhookSecret: stripeConfig.WebhookSecret, }) - // Calculate the amount with exchange rate + + // Convert order amount to CNY using current exchange rate amount, err := l.queryExchangeRate("CNY", info.Amount) if err != nil { - l.Errorw("[CheckoutOrderLogic] queryExchangeRate error", logger.Field("error", err.Error())) + l.Errorw("[PurchaseCheckout] queryExchangeRate error", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "queryExchangeRate error: %s", err.Error()) } - convertAmount := int64(amount * 100) - // create payment + convertAmount := int64(amount * 100) // Convert to cents for Stripe API + + // Create Stripe payment sheet for client-side processing result, err := client.CreatePaymentSheet(&stripe.Order{ OrderNo: info.OrderNo, Subscribe: strconv.FormatInt(info.SubscribeId, 10), @@ -204,37 +236,46 @@ func (l *PurchaseCheckoutLogic) stripePayment(config string, info *order.Order, Email: identifier, }) if err != nil { - l.Errorw("[CheckoutOrderLogic] CreatePaymentSheet error", logger.Field("error", err.Error())) + l.Errorw("[PurchaseCheckout] CreatePaymentSheet error", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "CreatePaymentSheet error: %s", err.Error()) } - tradeNo := result.TradeNo + + // Prepare response data for client-side Stripe integration stripePayment := &types.StripePayment{ PublishableKey: stripeConfig.PublicKey, ClientSecret: result.ClientSecret, Method: stripeConfig.Payment, } - // save payment - info.TradeNo = tradeNo + + // Save Stripe trade number to order for tracking + info.TradeNo = result.TradeNo err = l.svcCtx.OrderModel.Update(l.ctx, info) if err != nil { - l.Errorw("[CheckoutOrderLogic] Update error", logger.Field("error", err.Error())) + l.Errorw("[PurchaseCheckout] Update order error", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Update error: %s", err.Error()) } return stripePayment, nil } +// epayPayment processes EPay payment by generating a payment URL for redirect +// It handles currency conversion and creates a payment URL for external payment processing func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order.Order, returnUrl string) (string, error) { - epayConfig := payment.EPayConfig{} - if err := json.Unmarshal([]byte(config.Config), &epayConfig); err != nil { - l.Errorw("[CheckoutOrderLogic] Unmarshal error", logger.Field("error", err.Error())) + // Parse EPay configuration from payment settings + epayConfig := &payment.EPayConfig{} + if err := epayConfig.Unmarshal([]byte(config.Config)); err != nil { + l.Errorw("[PurchaseCheckout] Unmarshal EPay config error", logger.Field("error", err.Error())) return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error()) } + // Initialize EPay client with merchant credentials client := epay.NewClient(epayConfig.Pid, epayConfig.Url, epayConfig.Key) - // Calculate the amount with exchange rate + + // Convert order amount to CNY using current exchange rate amount, err := l.queryExchangeRate("CNY", info.Amount) if err != nil { return "", err } + + // Build notification URL for payment status callbacks notifyUrl := "" if config.Domain != "" { notifyUrl = config.Domain + "/v1/notify/" + config.Platform + "/" + config.Token @@ -245,7 +286,8 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order } notifyUrl = "https://" + host + "/v1/notify/" + config.Platform + "/" + config.Token } - // create payment + + // Create payment URL for user redirection url := client.CreatePayUrl(epay.Order{ Name: l.svcCtx.Config.Site.SiteName, Amount: amount, @@ -257,19 +299,25 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order return url, nil } -func (l *PurchaseCheckoutLogic) payssionPayment(config *payment.Payment, info *order.Order, returnUrl string) (string, error) { - payssionConfig := payment.PayssionConfig{} - if err := json.Unmarshal([]byte(config.Config), &payssionConfig); err != nil { - l.Errorw("[CheckoutOrderLogic] payssionPayment Unmarshal error", logger.Field("error", err.Error())) - return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), " payssionPaymentUnmarshal error: %s", err.Error()) +// CryptoSaaSPayment processes CryptoSaaSPayment payment by generating a payment URL for redirect +// It handles currency conversion and creates a payment URL for external payment processing +func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info *order.Order, returnUrl string) (string, error) { + // Parse EPay configuration from payment settings + epayConfig := &payment.CryptoSaaSConfig{} + if err := epayConfig.Unmarshal([]byte(config.Config)); err != nil { + l.Errorw("[PurchaseCheckout] Unmarshal EPay config error", logger.Field("error", err.Error())) + return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error()) } - client := payssion.NewClient(payssionConfig.ApiKey, payssionConfig.SecretKey, payssionConfig.PmId, payssionConfig.Currency, payssionConfig.QueryUrl, payssionConfig.CreateUrl) - // Calculate the amount with exchange rate + // Initialize EPay client with merchant credentials + client := epay.NewClient(epayConfig.AccountID, epayConfig.Endpoint, epayConfig.SecretKey) + + // Convert order amount to CNY using current exchange rate amount, err := l.queryExchangeRate("CNY", info.Amount) if err != nil { - l.Errorw("[CheckoutOrderLogic] payssionPayment queryExchangeRate error", logger.Field("error", err.Error())) return "", err } + + // Build notification URL for payment status callbacks notifyUrl := "" if config.Domain != "" { notifyUrl = config.Domain + "/v1/notify/" + config.Platform + "/" + config.Token @@ -280,37 +328,47 @@ func (l *PurchaseCheckoutLogic) payssionPayment(config *payment.Payment, info *o } notifyUrl = "https://" + host + "/v1/notify/" + config.Platform + "/" + config.Token } - // create payment - url, err := client.CreateOrder(payssion.Order{ + + // Create payment URL for user redirection + url := client.CreatePayUrl(epay.Order{ Name: l.svcCtx.Config.Site.SiteName, Amount: amount, OrderNo: info.OrderNo, + SignType: "MD5", NotifyUrl: notifyUrl, ReturnUrl: returnUrl, }) - return url, err + return url, nil } -// Query exchange rate +// queryExchangeRate converts the order amount from system currency to target currency +// It retrieves the current exchange rate and performs currency conversion if needed func (l *PurchaseCheckoutLogic) queryExchangeRate(to string, src int64) (amount float64, err error) { + // Convert cents to decimal amount amount = float64(src) / float64(100) - // query system currency + + // Retrieve system currency configuration currency, err := l.svcCtx.SystemModel.GetCurrencyConfig(l.ctx) if err != nil { - l.Errorw("[CheckoutOrderLogic] GetCurrencyConfig error", logger.Field("error", err.Error())) + l.Errorw("[PurchaseCheckout] GetCurrencyConfig error", logger.Field("error", err.Error())) return 0, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetCurrencyConfig error: %s", err.Error()) } + + // Parse currency configuration configs := struct { CurrencyUnit string CurrencySymbol string AccessKey string }{} tool.SystemConfigSliceReflectToStruct(currency, &configs) + + // Skip conversion if no exchange rate API key configured if configs.AccessKey == "" { return amount, nil } + + // Convert currency if system currency differs from target currency if configs.CurrencyUnit != to { - // query exchange rate result, err := exchangeRate.GetExchangeRete(configs.CurrencyUnit, to, configs.AccessKey, 1) if err != nil { return 0, err @@ -320,59 +378,149 @@ func (l *PurchaseCheckoutLogic) queryExchangeRate(to string, src int64) (amount return amount, nil } -// Balance payment +// balancePayment processes balance payment with gift amount priority logic +// It prioritizes using gift amount first, then regular balance, and creates proper audit logs func (l *PurchaseCheckoutLogic) balancePayment(u *user.User, o *order.Order) error { var userInfo user.User - err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { + var err error + if o.Amount == 0 { + // No payment required for zero-amount orders + l.Logger.Info( + "[PurchaseCheckout] No payment required for zero-amount order", + logger.Field("orderNo", o.OrderNo), + logger.Field("userId", u.Id), + ) + err = l.svcCtx.OrderModel.UpdateOrderStatus(l.ctx, o.OrderNo, 2) + if err != nil { + l.Errorw("[PurchaseCheckout] Update order status error", + logger.Field("error", err.Error()), + logger.Field("orderNo", o.OrderNo), + logger.Field("userId", u.Id)) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Update order status error: %s", err.Error()) + } + goto activation + } + + err = l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { + // Retrieve latest user information with row-level locking err := db.Model(&user.User{}).Where("id = ?", u.Id).First(&userInfo).Error if err != nil { return err } - if userInfo.Balance < o.Amount { - return errors.Wrapf(xerr.NewErrCode(xerr.InsufficientBalance), "Insufficient balance") + // Check if user has sufficient total balance (regular + gift) + totalAvailable := userInfo.Balance + userInfo.GiftAmount + if totalAvailable < o.Amount { + return errors.Wrapf(xerr.NewErrCode(xerr.InsufficientBalance), + "Insufficient balance: required %d, available %d", o.Amount, totalAvailable) } - // deduct balance - userInfo.Balance -= o.Amount + + // Calculate payment distribution: prioritize gift amount first + var giftUsed, balanceUsed int64 + remainingAmount := o.Amount + + if userInfo.GiftAmount >= remainingAmount { + // Gift amount covers the entire payment + giftUsed = remainingAmount + balanceUsed = 0 + } else { + // Use all available gift amount, then regular balance + giftUsed = userInfo.GiftAmount + balanceUsed = remainingAmount - giftUsed + } + + // Update user balances + userInfo.GiftAmount -= giftUsed + userInfo.Balance -= balanceUsed + + // Save updated user information err = l.svcCtx.UserModel.Update(l.ctx, &userInfo) if err != nil { return err } - // create balance log - balanceLog := &user.BalanceLog{ - Id: 0, - UserId: u.Id, - Amount: o.Amount, - Type: 3, - OrderId: o.Id, - Balance: userInfo.Balance, + + // Create gift amount log if gift amount was used + if giftUsed > 0 { + giftLog := &log.Gift{ + OrderNo: o.OrderNo, + Type: log.GiftTypeReduce, // Type 2 represents gift amount decrease/usage + Amount: giftUsed, + Balance: userInfo.GiftAmount, + Remark: "Purchase payment", + } + content, _ := giftLog.Marshal() + + err = db.Create(&log.SystemLog{ + Type: log.TypeGift.Uint8(), + ObjectID: userInfo.Id, + Date: time.Now().Format(time.DateOnly), + Content: string(content), + }).Error + if err != nil { + return err + } } - err = db.Create(balanceLog).Error + + // Create balance log if regular balance was used + if balanceUsed > 0 { + balanceLog := &log.Balance{ + Amount: balanceUsed, + Type: log.BalanceTypePayment, // Type 3 represents payment deduction + OrderNo: o.OrderNo, + Balance: userInfo.Balance, + Timestamp: time.Now().UnixMilli(), + } + content, _ := balanceLog.Marshal() + err = db.Create(&log.SystemLog{ + Type: log.TypeBalance.Uint8(), + ObjectID: userInfo.Id, + Date: time.Now().Format(time.DateOnly), + Content: string(content), + }).Error + if err != nil { + return err + } + } + + // Store gift amount used in order for potential refund tracking + o.GiftAmount = giftUsed + err = l.svcCtx.OrderModel.Update(l.ctx, o, db) if err != nil { return err } - return l.svcCtx.OrderModel.UpdateOrderStatus(l.ctx, o.OrderNo, 2) + + // Mark order as paid (status = 2) + return l.svcCtx.OrderModel.UpdateOrderStatus(l.ctx, o.OrderNo, 2, db) }) + if err != nil { - l.Errorw("[CheckoutOrderLogic] Transaction error", logger.Field("error", err.Error()), logger.Field("orderNo", o.OrderNo)) + l.Errorw("[PurchaseCheckout] Balance payment transaction error", + logger.Field("error", err.Error()), + logger.Field("orderNo", o.OrderNo), + logger.Field("userId", u.Id)) return err } - // create activity order task + +activation: + // Enqueue order activation task for immediate processing payload := queueType.ForthwithActivateOrderPayload{ OrderNo: o.OrderNo, } bytes, err := json.Marshal(payload) if err != nil { - l.Errorw("[CheckoutOrderLogic] Marshal error", logger.Field("error", err.Error())) + l.Errorw("[PurchaseCheckout] Marshal activation payload error", logger.Field("error", err.Error())) return err } task := asynq.NewTask(queueType.ForthwithActivateOrder, bytes) _, err = l.svcCtx.Queue.EnqueueContext(l.ctx, task) if err != nil { - l.Errorw("[CheckoutOrderLogic] Enqueue error", logger.Field("error", err.Error())) + l.Errorw("[PurchaseCheckout] Enqueue activation task error", logger.Field("error", err.Error())) return err } - l.Logger.Info("[CheckoutOrderLogic] Enqueue success", logger.Field("orderNo", o.OrderNo)) + + l.Logger.Info("[PurchaseCheckout] Balance payment completed successfully", + logger.Field("orderNo", o.OrderNo), + logger.Field("userId", u.Id)) return nil } diff --git a/internal/logic/public/portal/purchaseLogic.go b/internal/logic/public/portal/purchaseLogic.go index 2f445a2..2c0cd69 100644 --- a/internal/logic/public/portal/purchaseLogic.go +++ b/internal/logic/public/portal/purchaseLogic.go @@ -6,18 +6,17 @@ import ( "fmt" "time" - "github.com/perfect-panel/ppanel-server/pkg/payment" - - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/internal/model/order" + "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/payment" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" + queue "github.com/perfect-panel/server/queue/types" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - queue "github.com/perfect-panel/ppanel-server/queue/types" "github.com/pkg/errors" "gorm.io/gorm" ) @@ -71,7 +70,7 @@ func (l *PurchaseLogic) Purchase(req *types.PortalPurchaseRequest) (resp *types. amount := int64(float64(price) * discount) discountAmount := price - amount - var coupon int64 = 0 + var couponAmount int64 = 0 // Calculate the coupon deduction if req.Coupon != "" { couponInfo, err := l.svcCtx.CouponModel.FindOneByCode(l.ctx, req.Coupon) @@ -81,18 +80,18 @@ func (l *PurchaseLogic) Purchase(req *types.PortalPurchaseRequest) (resp *types. } return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find coupon error: %v", err.Error()) } - if couponInfo.Count <= couponInfo.UsedCount { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponUsed), "coupon used") + if couponInfo.Count != 0 && couponInfo.Count <= couponInfo.UsedCount { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponInsufficientUsage), "coupon used") } couponSub := tool.StringToInt64Slice(couponInfo.Subscribe) if len(couponSub) > 0 && !tool.Contains(couponSub, req.SubscribeId) { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponNotMatch), "coupon not match") + return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponNotApplicable), "coupon not match") } - coupon = calculateCoupon(amount, couponInfo) + + couponAmount = calculateCoupon(amount, couponInfo) } // Calculate the handling fee - amount -= coupon - var deductionAmount int64 + amount -= couponAmount // find payment method paymentConfig, err := l.svcCtx.PaymentModel.FindOne(l.ctx, req.Payment) if err != nil { @@ -117,9 +116,9 @@ func (l *PurchaseLogic) Purchase(req *types.PortalPurchaseRequest) (resp *types. Price: price, Amount: amount, Discount: discountAmount, - GiftAmount: deductionAmount, + GiftAmount: 0, Coupon: req.Coupon, - CouponDiscount: coupon, + CouponDiscount: couponAmount, PaymentId: req.Payment, Method: paymentConfig.Platform, FeeAmount: feeAmount, @@ -135,14 +134,17 @@ func (l *PurchaseLogic) Purchase(req *types.PortalPurchaseRequest) (resp *types. Identifier: req.Identifier, AuthType: req.AuthType, Password: req.Password, + InviteCode: req.InviteCode, } - if _, err = l.svcCtx.Redis.Set(l.ctx, fmt.Sprintf(constant.TempOrderCacheKey, orderInfo.OrderNo), tempOrder.Marshal(), CloseOrderTimeMinutes*time.Minute).Result(); err != nil { + content, _ := tempOrder.Marshal() + + if _, err = l.svcCtx.Redis.Set(l.ctx, fmt.Sprintf(constant.TempOrderCacheKey, orderInfo.OrderNo), string(content), CloseOrderTimeMinutes*time.Minute).Result(); err != nil { l.Errorw("[Purchase] Redis set error", logger.Field("error", err.Error()), logger.Field("order_no", orderInfo.OrderNo)) return err } l.Infow("[Purchase] Guest order", logger.Field("order_no", orderInfo.OrderNo), logger.Field("identifier", req.Identifier)) // save guest order - if err := l.svcCtx.OrderModel.Insert(l.ctx, orderInfo, tx); err != nil { + if err = l.svcCtx.OrderModel.Insert(l.ctx, orderInfo, tx); err != nil { return err } return nil diff --git a/internal/logic/public/portal/queryPurchaseOrderLogic.go b/internal/logic/public/portal/queryPurchaseOrderLogic.go index fc67dc4..d8e4795 100644 --- a/internal/logic/public/portal/queryPurchaseOrderLogic.go +++ b/internal/logic/public/portal/queryPurchaseOrderLogic.go @@ -6,18 +6,18 @@ import ( "fmt" "time" - "github.com/perfect-panel/ppanel-server/internal/model/order" + "github.com/perfect-panel/server/internal/model/order" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/pkg/tool" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/jwt" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/constant" + "github.com/perfect-panel/server/pkg/jwt" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/uuidx" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) @@ -95,7 +95,7 @@ func (l *QueryPurchaseOrderLogic) handleTemporaryOrder(orderInfo *order.Order, r } // Validate user and email - if err := l.validateUserAndEmail(orderInfo, req.Identifier, req.Identifier); err != nil { + if err = l.validateUserAndEmail(orderInfo, req.AuthType, req.Identifier); err != nil { return "", err } diff --git a/internal/logic/public/portal/tool.go b/internal/logic/public/portal/tool.go index 521632d..c2d2bbd 100644 --- a/internal/logic/public/portal/tool.go +++ b/internal/logic/public/portal/tool.go @@ -1,9 +1,9 @@ package portal import ( - "github.com/perfect-panel/ppanel-server/internal/model/coupon" - "github.com/perfect-panel/ppanel-server/internal/model/payment" - "github.com/perfect-panel/ppanel-server/internal/types" + "github.com/perfect-panel/server/internal/model/coupon" + "github.com/perfect-panel/server/internal/model/payment" + "github.com/perfect-panel/server/internal/types" ) func getDiscount(discounts []types.SubscribeDiscount, inputMonths int64) float64 { diff --git a/internal/logic/public/subscribe/queryApplicationConfigLogic.go b/internal/logic/public/subscribe/queryApplicationConfigLogic.go deleted file mode 100644 index b5a913d..0000000 --- a/internal/logic/public/subscribe/queryApplicationConfigLogic.go +++ /dev/null @@ -1,116 +0,0 @@ -package subscribe - -import ( - "context" - - "github.com/perfect-panel/ppanel-server/internal/model/application" - "github.com/perfect-panel/ppanel-server/pkg/xerr" - "github.com/pkg/errors" - "gorm.io/gorm" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -type QueryApplicationConfigLogic struct { - logger.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// Get application config -func NewQueryApplicationConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryApplicationConfigLogic { - return &QueryApplicationConfigLogic{ - Logger: logger.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *QueryApplicationConfigLogic) QueryApplicationConfig() (resp *types.ApplicationResponse, err error) { - resp = &types.ApplicationResponse{} - var applications []*application.Application - err = l.svcCtx.ApplicationModel.Transaction(l.ctx, func(tx *gorm.DB) (err error) { - return tx.Model(applications).Preload("ApplicationVersions").Find(&applications).Error - }) - if err != nil { - l.Errorw("[QueryApplicationConfig] get application error: ", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get application error: %v", err.Error()) - } - - if len(applications) == 0 { - return resp, nil - } - - for _, app := range applications { - applicationResponse := types.ApplicationResponseInfo{ - Id: app.Id, - Name: app.Name, - Icon: app.Icon, - Description: app.Description, - SubscribeType: app.SubscribeType, - } - applicationVersions := app.ApplicationVersions - if len(applicationVersions) != 0 { - for _, applicationVersion := range applicationVersions { - /*if !applicationVersion.IsDefault { - continue - }*/ - switch applicationVersion.Platform { - case "ios": - applicationResponse.Platform.IOS = append(applicationResponse.Platform.IOS, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "macos": - applicationResponse.Platform.MacOS = append(applicationResponse.Platform.MacOS, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "linux": - applicationResponse.Platform.Linux = append(applicationResponse.Platform.Linux, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "android": - applicationResponse.Platform.Android = append(applicationResponse.Platform.Android, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "windows": - applicationResponse.Platform.Windows = append(applicationResponse.Platform.Windows, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - case "harmony": - applicationResponse.Platform.Harmony = append(applicationResponse.Platform.Harmony, &types.ApplicationVersion{ - Id: applicationVersion.Id, - Url: applicationVersion.Url, - Version: applicationVersion.Version, - IsDefault: applicationVersion.IsDefault, - Description: applicationVersion.Description, - }) - } - } - } - resp.Applications = append(resp.Applications, applicationResponse) - } - - return -} diff --git a/internal/logic/public/subscribe/querySubscribeGroupListLogic.go b/internal/logic/public/subscribe/querySubscribeGroupListLogic.go index d0f08ac..0f7c947 100644 --- a/internal/logic/public/subscribe/querySubscribeGroupListLogic.go +++ b/internal/logic/public/subscribe/querySubscribeGroupListLogic.go @@ -3,12 +3,12 @@ package subscribe import ( "context" - "github.com/perfect-panel/ppanel-server/internal/model/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/subscribe" + "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" ) diff --git a/internal/logic/public/subscribe/querySubscribeListLogic.go b/internal/logic/public/subscribe/querySubscribeListLogic.go index c51b5a9..2208559 100644 --- a/internal/logic/public/subscribe/querySubscribeListLogic.go +++ b/internal/logic/public/subscribe/querySubscribeListLogic.go @@ -4,11 +4,12 @@ import ( "context" "encoding/json" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/subscribe" + "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" ) @@ -27,15 +28,22 @@ func NewQuerySubscribeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) } } -func (l *QuerySubscribeListLogic) QuerySubscribeList() (resp *types.QuerySubscribeListResponse, err error) { +func (l *QuerySubscribeListLogic) QuerySubscribeList(req *types.QuerySubscribeListRequest) (resp *types.QuerySubscribeListResponse, err error) { - data, err := l.svcCtx.SubscribeModel.QuerySubscribeList(l.ctx) + total, data, err := l.svcCtx.SubscribeModel.FilterList(l.ctx, &subscribe.FilterParams{ + Page: 1, + Size: 9999, + Language: req.Language, + Sell: true, + DefaultLanguage: true, + }) if err != nil { l.Errorw("[QuerySubscribeListLogic] Database Error", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "QuerySubscribeList error: %v", err.Error()) } + resp = &types.QuerySubscribeListResponse{ - Total: int64(len(data)), + Total: total, } list := make([]types.Subscribe, len(data)) for i, item := range data { diff --git a/internal/logic/public/ticket/createUserTicketFollowLogic.go b/internal/logic/public/ticket/createUserTicketFollowLogic.go index c540931..06574c0 100644 --- a/internal/logic/public/ticket/createUserTicketFollowLogic.go +++ b/internal/logic/public/ticket/createUserTicketFollowLogic.go @@ -3,14 +3,14 @@ package ticket import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/ticket" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/ticket" + "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/xerr" "github.com/pkg/errors" ) diff --git a/internal/logic/public/ticket/createUserTicketLogic.go b/internal/logic/public/ticket/createUserTicketLogic.go index 51ce680..ea6cda1 100644 --- a/internal/logic/public/ticket/createUserTicketLogic.go +++ b/internal/logic/public/ticket/createUserTicketLogic.go @@ -3,16 +3,16 @@ package ticket import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/ticket" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/ticket" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type CreateUserTicketLogic struct { diff --git a/internal/logic/public/ticket/getUserTicketDetailsLogic.go b/internal/logic/public/ticket/getUserTicketDetailsLogic.go index b22b3d5..681e7fb 100644 --- a/internal/logic/public/ticket/getUserTicketDetailsLogic.go +++ b/internal/logic/public/ticket/getUserTicketDetailsLogic.go @@ -3,14 +3,14 @@ package ticket import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/public/ticket/getUserTicketListLogic.go b/internal/logic/public/ticket/getUserTicketListLogic.go index fdaa92c..6340259 100644 --- a/internal/logic/public/ticket/getUserTicketListLogic.go +++ b/internal/logic/public/ticket/getUserTicketListLogic.go @@ -3,15 +3,15 @@ package ticket import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) diff --git a/internal/logic/public/ticket/updateUserTicketStatusLogic.go b/internal/logic/public/ticket/updateUserTicketStatusLogic.go index acf71f7..2748a8d 100644 --- a/internal/logic/public/ticket/updateUserTicketStatusLogic.go +++ b/internal/logic/public/ticket/updateUserTicketStatusLogic.go @@ -3,13 +3,13 @@ package ticket import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/xerr" "github.com/pkg/errors" ) diff --git a/internal/logic/public/user/bindOAuthCallbackLogic.go b/internal/logic/public/user/bindOAuthCallbackLogic.go index dc4044f..26e8e0e 100644 --- a/internal/logic/public/user/bindOAuthCallbackLogic.go +++ b/internal/logic/public/user/bindOAuthCallbackLogic.go @@ -5,17 +5,17 @@ import ( "encoding/json" "fmt" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/auth" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/oauth/apple" - "github.com/perfect-panel/ppanel-server/pkg/oauth/google" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/auth" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/oauth/apple" + "github.com/perfect-panel/server/pkg/oauth/google" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" ) @@ -52,8 +52,10 @@ func (l *BindOAuthCallbackLogic) BindOAuthCallback(req *types.BindOAuthCallbackR err = l.google(req) case "apple": err = l.apple(req) + case "telegram": + err = l.telegram(req) default: - l.Errorw("oauth login method not support: %v", logger.Field("method", req.Method)) + l.Errorw("oauth login method not support", logger.Field("method", req.Method)) return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "oauth login method not support: %v", req.Method) } if err != nil { @@ -212,3 +214,7 @@ func (l *BindOAuthCallbackLogic) apple(req *types.BindOAuthCallbackRequest) erro } return nil } + +func (l *BindOAuthCallbackLogic) telegram(req *types.BindOAuthCallbackRequest) error { + return nil +} diff --git a/internal/logic/public/user/bindOAuthLogic.go b/internal/logic/public/user/bindOAuthLogic.go index b6acfe1..c2d9778 100644 --- a/internal/logic/public/user/bindOAuthLogic.go +++ b/internal/logic/public/user/bindOAuthLogic.go @@ -5,16 +5,16 @@ import ( "fmt" "time" - "github.com/perfect-panel/ppanel-server/internal/model/auth" - "github.com/perfect-panel/ppanel-server/pkg/oauth/google" - "github.com/perfect-panel/ppanel-server/pkg/random" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/auth" + "github.com/perfect-panel/server/pkg/oauth/google" + "github.com/perfect-panel/server/pkg/random" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "golang.org/x/oauth2" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type BindOAuthLogic struct { diff --git a/internal/logic/public/user/bindTelegramLogic.go b/internal/logic/public/user/bindTelegramLogic.go index 5fefe08..cb1a3b1 100644 --- a/internal/logic/public/user/bindTelegramLogic.go +++ b/internal/logic/public/user/bindTelegramLogic.go @@ -5,9 +5,9 @@ import ( "fmt" "time" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type BindTelegramLogic struct { diff --git a/internal/logic/public/user/calculateRemainingAmount.go b/internal/logic/public/user/calculateRemainingAmount.go index 9559956..d601c2e 100644 --- a/internal/logic/public/user/calculateRemainingAmount.go +++ b/internal/logic/public/user/calculateRemainingAmount.go @@ -3,11 +3,11 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/deduction" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/deduction" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) @@ -38,6 +38,7 @@ func CalculateRemainingAmount(ctx context.Context, svcCtx *svc.ServiceContext, u orderQuantity := orderDetails.Quantity // Calculate Order Amount orderAmount := orderDetails.Amount + orderDetails.GiftAmount + if len(orderDetails.SubOrders) > 0 { for _, subOrder := range orderDetails.SubOrders { if subOrder.Status == 2 || subOrder.Status == 5 { @@ -47,7 +48,7 @@ func CalculateRemainingAmount(ctx context.Context, svcCtx *svc.ServiceContext, u } } // Calculate Remaining Amount - remainingAmount := deduction.CalculateRemainingAmount( + remainingAmount, err := deduction.CalculateRemainingAmount( deduction.Subscribe{ StartTime: userSubscribe.StartTime, ExpireTime: userSubscribe.ExpireTime, @@ -64,5 +65,8 @@ func CalculateRemainingAmount(ctx context.Context, svcCtx *svc.ServiceContext, u Quantity: orderQuantity, }, ) + if err != nil { + return 0, errors.Wrapf(xerr.NewErrCode(500), "CalculateRemainingAmount failed, userSubscribeId: %d, err: %v", userSubscribeId, err) + } return remainingAmount, nil } diff --git a/internal/logic/public/user/getDeviceListLogic.go b/internal/logic/public/user/getDeviceListLogic.go new file mode 100644 index 0000000..76722d5 --- /dev/null +++ b/internal/logic/public/user/getDeviceListLogic.go @@ -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 +} diff --git a/internal/logic/public/user/getLoginLogLogic.go b/internal/logic/public/user/getLoginLogLogic.go index e498911..a6637f4 100644 --- a/internal/logic/public/user/getLoginLogLogic.go +++ b/internal/logic/public/user/getLoginLogLogic.go @@ -3,14 +3,14 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/xerr" "github.com/pkg/errors" ) @@ -35,15 +35,34 @@ func (l *GetLoginLogLogic) GetLoginLog(req *types.GetLoginLogRequest) (resp *typ logger.Error("current user is not found in context") return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access") } - data, total, err := l.svcCtx.UserModel.FilterLoginLogList(l.ctx, req.Page, req.Size, &user.LoginLogFilterParams{ - UserId: u.Id, + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Type: log.TypeLogin.Uint8(), + ObjectID: u.Id, }) if err != nil { l.Errorw("find login log failed:", logger.Field("error", err.Error()), logger.Field("user_id", u.Id)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find login log failed: %v", err.Error()) } list := make([]types.UserLoginLog, 0) - tool.DeepCopy(&list, data) + + for _, datum := range data { + var content log.Login + if err = content.Unmarshal([]byte(datum.Content)); err != nil { + l.Errorf("[GetUserLoginLogs] unmarshal login log content failed: %v", err.Error()) + continue + } + list = append(list, types.UserLoginLog{ + Id: datum.Id, + UserId: datum.ObjectID, + LoginIP: content.LoginIP, + UserAgent: content.UserAgent, + Success: content.Success, + Timestamp: datum.CreatedAt.UnixMilli(), + }) + } + return &types.GetLoginLogResponse{ Total: total, List: list, diff --git a/internal/logic/public/user/getOAuthMethodsLogic.go b/internal/logic/public/user/getOAuthMethodsLogic.go index 3ad0f2b..7678380 100644 --- a/internal/logic/public/user/getOAuthMethodsLogic.go +++ b/internal/logic/public/user/getOAuthMethodsLogic.go @@ -3,14 +3,14 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/public/user/getSubscribeLogLogic.go b/internal/logic/public/user/getSubscribeLogLogic.go index b495c7a..eeb51b9 100644 --- a/internal/logic/public/user/getSubscribeLogLogic.go +++ b/internal/logic/public/user/getSubscribeLogLogic.go @@ -3,14 +3,14 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/xerr" "github.com/pkg/errors" ) @@ -20,7 +20,7 @@ type GetSubscribeLogLogic struct { svcCtx *svc.ServiceContext } -// Get Subscribe Log +// NewGetSubscribeLogLogic Get Subscribe Log func NewGetSubscribeLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetSubscribeLogLogic { return &GetSubscribeLogLogic{ Logger: logger.WithContext(ctx), @@ -35,15 +35,34 @@ func (l *GetSubscribeLogLogic) GetSubscribeLog(req *types.GetSubscribeLogRequest logger.Error("current user is not found in context") return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access") } - data, total, err := l.svcCtx.UserModel.FilterSubscribeLogList(l.ctx, req.Page, req.Size, &user.SubscribeLogFilterParams{ - UserId: u.Id, + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Type: log.TypeSubscribe.Uint8(), + ObjectID: u.Id, // filter by current user id }) if err != nil { l.Errorw("[GetUserSubscribeLogs] Get User Subscribe Logs Error:", logger.Field("err", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Get User Subscribe Logs Error") } var list []types.UserSubscribeLog - tool.DeepCopy(&list, data) + + for _, item := range data { + var content log.Subscribe + if err = content.Unmarshal([]byte(item.Content)); err != nil { + l.Errorf("[GetUserSubscribeLogs] unmarshal subscribe log content failed: %v", err.Error()) + continue + } + list = append(list, types.UserSubscribeLog{ + Id: item.Id, + UserId: item.ObjectID, + UserSubscribeId: content.UserSubscribeId, + Token: content.Token, + IP: content.ClientIP, + UserAgent: content.UserAgent, + Timestamp: item.CreatedAt.UnixMilli(), + }) + } return &types.GetSubscribeLogResponse{ List: list, diff --git a/internal/logic/public/user/preUnsubscribeLogic.go b/internal/logic/public/user/preUnsubscribeLogic.go index 2554a4a..729dcbc 100644 --- a/internal/logic/public/user/preUnsubscribeLogic.go +++ b/internal/logic/public/user/preUnsubscribeLogic.go @@ -3,9 +3,9 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type PreUnsubscribeLogic struct { diff --git a/internal/logic/public/user/queryUserAffiliateListLogic.go b/internal/logic/public/user/queryUserAffiliateListLogic.go index 5182b77..b645270 100644 --- a/internal/logic/public/user/queryUserAffiliateListLogic.go +++ b/internal/logic/public/user/queryUserAffiliateListLogic.go @@ -3,16 +3,16 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type QueryUserAffiliateListLogic struct { diff --git a/internal/logic/public/user/queryUserAffiliateLogic.go b/internal/logic/public/user/queryUserAffiliateLogic.go index 5240fc9..7c8e731 100644 --- a/internal/logic/public/user/queryUserAffiliateLogic.go +++ b/internal/logic/public/user/queryUserAffiliateLogic.go @@ -3,16 +3,17 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type QueryUserAffiliateLogic struct { @@ -44,14 +45,20 @@ func (l *QueryUserAffiliateLogic) QueryUserAffiliate() (resp *types.QueryUserAff if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query User Affiliate failed: %v", err) } - err = l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { - return db.Model(&user.CommissionLog{}). - Where("user_id = ?", u.Id). - Select("COALESCE(SUM(amount), 0)"). - Scan(&sum).Error + data, _, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: 1, + Size: 99999, + Type: log.TypeCommission.Uint8(), + ObjectID: u.Id, }) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query User Affiliate failed: %v", err) + + for _, datum := range data { + content := log.Commission{} + if err = content.Unmarshal([]byte(datum.Content)); err != nil { + l.Errorf("[QueryUserAffiliate] unmarshal comission log failed: %v", err.Error()) + continue + } + sum += content.Amount } return &types.QueryUserAffiliateCountResponse{ diff --git a/internal/logic/public/user/queryUserBalanceLogLogic.go b/internal/logic/public/user/queryUserBalanceLogLogic.go index a48f267..e8c6d8f 100644 --- a/internal/logic/public/user/queryUserBalanceLogLogic.go +++ b/internal/logic/public/user/queryUserBalanceLogLogic.go @@ -3,17 +3,15 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/xerr" "github.com/pkg/errors" - "gorm.io/gorm" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" ) type QueryUserBalanceLogLogic struct { @@ -22,7 +20,7 @@ type QueryUserBalanceLogLogic struct { svcCtx *svc.ServiceContext } -// Query User Balance Log +// NewQueryUserBalanceLogLogic Query User Balance Log func NewQueryUserBalanceLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryUserBalanceLogLogic { return &QueryUserBalanceLogLogic{ Logger: logger.WithContext(ctx), @@ -37,19 +35,37 @@ func (l *QueryUserBalanceLogLogic) QueryUserBalanceLog() (resp *types.QueryUserB logger.Error("current user is not found in context") return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access") } - var data []*user.BalanceLog - var total int64 - // Query User Balance Log - err = l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { - return db.Model(&user.BalanceLog{}).Order("created_at DESC").Where("user_id = ?", u.Id).Count(&total).Find(&data).Error + + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: 1, + Size: 99999, + Type: log.TypeBalance.Uint8(), + ObjectID: u.Id, }) if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query User Balance Log failed: %v", err) + l.Errorw("[QueryUserBalanceLog] Query User Balance Log Error:", logger.Field("err", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query User Balance Log Error") } - resp = &types.QueryUserBalanceLogListResponse{ - List: make([]types.UserBalanceLog, 0), + + list := make([]types.BalanceLog, 0) + for _, datum := range data { + var content log.Balance + if err = content.Unmarshal([]byte(datum.Content)); err != nil { + l.Errorf("[QueryUserBalanceLog] unmarshal balance log content failed: %v", err.Error()) + continue + } + list = append(list, types.BalanceLog{ + UserId: datum.ObjectID, + Amount: content.Amount, + Type: content.Type, + OrderNo: content.OrderNo, + Balance: content.Balance, + Timestamp: content.Timestamp, + }) + } + + return &types.QueryUserBalanceLogListResponse{ Total: total, - } - tool.DeepCopy(&resp.List, data) - return + List: list, + }, nil } diff --git a/internal/logic/public/user/queryUserCommissionLogLogic.go b/internal/logic/public/user/queryUserCommissionLogLogic.go index f4c81e7..c005828 100644 --- a/internal/logic/public/user/queryUserCommissionLogLogic.go +++ b/internal/logic/public/user/queryUserCommissionLogLogic.go @@ -3,17 +3,15 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/xerr" "github.com/pkg/errors" - "gorm.io/gorm" - - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" ) type QueryUserCommissionLogLogic struct { @@ -32,22 +30,40 @@ func NewQueryUserCommissionLogLogic(ctx context.Context, svcCtx *svc.ServiceCont } func (l *QueryUserCommissionLogLogic) QueryUserCommissionLog(req *types.QueryUserCommissionLogListRequest) (resp *types.QueryUserCommissionLogListResponse, err error) { - var data []*user.CommissionLog 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") } - err = l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { - return db.Order("id desc").Limit(req.Size).Offset((req.Page-1)*req.Size).Where("user_id = ?", u.Id).Find(&data).Error + data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{ + Page: req.Page, + Size: req.Size, + Type: log.TypeCommission.Uint8(), + ObjectID: u.Id, }) if err != nil { l.Errorw("Query User Commission Log failed", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query User Commission Log failed: %v", err) } var list []types.CommissionLog - tool.DeepCopy(&list, data) + + for _, datum := range data { + var content log.Commission + if err = content.Unmarshal([]byte(datum.Content)); err != nil { + l.Errorf("unmarshal commission log content failed: %v", err.Error()) + continue + } + list = append(list, types.CommissionLog{ + UserId: datum.ObjectID, + Type: content.Type, + Amount: content.Amount, + OrderNo: content.OrderNo, + Timestamp: content.Timestamp, + }) + } + return &types.QueryUserCommissionLogListResponse{ - List: list, + List: list, + Total: total, }, nil } diff --git a/internal/logic/public/user/queryUserInfoLogic.go b/internal/logic/public/user/queryUserInfoLogic.go index 8cbd54a..cf51020 100644 --- a/internal/logic/public/user/queryUserInfoLogic.go +++ b/internal/logic/public/user/queryUserInfoLogic.go @@ -2,17 +2,18 @@ package user import ( "context" + "sort" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/pkg/constant" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/phone" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "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/phone" + "github.com/perfect-panel/server/pkg/tool" ) type QueryUserInfoLogic struct { @@ -53,10 +54,31 @@ func (l *QueryUserInfoLogic) QueryUserInfo() (resp *types.User, err error) { } userMethods = append(userMethods, item) } + + // 按照指定顺序排序:email第一位,mobile第二位,其他按原顺序 + sort.Slice(userMethods, func(i, j int) bool { + return getAuthTypePriority(userMethods[i].AuthType) < getAuthTypePriority(userMethods[j].AuthType) + }) + resp.AuthMethods = userMethods return resp, nil } +// getAuthTypePriority 获取认证类型的排序优先级 +// email: 1 (第一位) +// mobile: 2 (第二位) +// 其他类型: 100+ (后续位置) +func getAuthTypePriority(authType string) int { + switch authType { + case "email": + return 1 + case "mobile": + return 2 + default: + return 100 + } +} + // maskOpenID 脱敏 OpenID,只保留前 3 和后 3 位 func maskOpenID(openID string) string { length := len(openID) diff --git a/internal/logic/public/user/queryUserSubscribeLogic.go b/internal/logic/public/user/queryUserSubscribeLogic.go index 372e19b..757f6fc 100644 --- a/internal/logic/public/user/queryUserSubscribeLogic.go +++ b/internal/logic/public/user/queryUserSubscribeLogic.go @@ -2,16 +2,17 @@ package user import ( "context" + "encoding/json" "time" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) @@ -36,7 +37,7 @@ func (l *QueryUserSubscribeLogic) QueryUserSubscribe() (resp *types.QueryUserSub logger.Error("current user is not found in context") return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access") } - data, err := l.svcCtx.UserModel.QueryUserSubscribe(l.ctx, u.Id, 1, 0) + data, err := l.svcCtx.UserModel.QueryUserSubscribe(l.ctx, u.Id, 0, 1, 2, 3) if err != nil { l.Errorw("[QueryUserSubscribeLogic] Query User Subscribe Error:", logger.Field("err", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query User Subscribe Error") @@ -50,6 +51,15 @@ func (l *QueryUserSubscribeLogic) QueryUserSubscribe() (resp *types.QueryUserSub for _, item := range data { var sub types.UserSubscribe tool.DeepCopy(&sub, item) + + // 解析Discount字段 避免在续订时只能续订一个月 + if item.Subscribe != nil && item.Subscribe.Discount != "" { + var discounts []types.SubscribeDiscount + if err := json.Unmarshal([]byte(item.Subscribe.Discount), &discounts); err == nil { + sub.Subscribe.Discount = discounts + } + } + sub.ResetTime = calculateNextResetTime(&sub) resp.List = append(resp.List, sub) } @@ -58,7 +68,7 @@ func (l *QueryUserSubscribeLogic) QueryUserSubscribe() (resp *types.QueryUserSub // 计算下次重置时间 func calculateNextResetTime(sub *types.UserSubscribe) int64 { - startTime := time.UnixMilli(sub.StartTime) + resetTime := time.UnixMilli(sub.ExpireTime) now := time.Now() switch sub.Subscribe.ResetCycle { case 0: @@ -66,15 +76,15 @@ func calculateNextResetTime(sub *types.UserSubscribe) int64 { case 1: return time.Date(now.Year(), now.Month()+1, 1, 0, 0, 0, 0, now.Location()).UnixMilli() case 2: - if startTime.Day() > now.Day() { - return time.Date(now.Year(), now.Month(), startTime.Day(), 0, 0, 0, 0, now.Location()).UnixMilli() + if resetTime.Day() > now.Day() { + return time.Date(now.Year(), now.Month(), resetTime.Day(), 0, 0, 0, 0, now.Location()).UnixMilli() } else { - return time.Date(now.Year(), now.Month()+1, startTime.Day(), 0, 0, 0, 0, now.Location()).UnixMilli() + return time.Date(now.Year(), now.Month()+1, resetTime.Day(), 0, 0, 0, 0, now.Location()).UnixMilli() } case 3: - targetTime := time.Date(now.Year(), startTime.Month(), startTime.Day(), 0, 0, 0, 0, now.Location()) + targetTime := time.Date(now.Year(), resetTime.Month(), resetTime.Day(), 0, 0, 0, 0, now.Location()) if targetTime.Before(now) { - targetTime = time.Date(now.Year()+1, startTime.Month(), startTime.Day(), 0, 0, 0, 0, now.Location()) + targetTime = time.Date(now.Year()+1, resetTime.Month(), resetTime.Day(), 0, 0, 0, 0, now.Location()) } return targetTime.UnixMilli() default: diff --git a/internal/logic/public/user/resetUserSubscribeTokenLogic.go b/internal/logic/public/user/resetUserSubscribeTokenLogic.go index 30bfd9e..febcae7 100644 --- a/internal/logic/public/user/resetUserSubscribeTokenLogic.go +++ b/internal/logic/public/user/resetUserSubscribeTokenLogic.go @@ -4,16 +4,18 @@ import ( "context" "time" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/internal/model/order" + + "github.com/perfect-panel/server/pkg/constant" "github.com/google/uuid" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/uuidx" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) @@ -47,12 +49,20 @@ func (l *ResetUserSubscribeTokenLogic) ResetUserSubscribeToken(req *types.ResetU 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") } + + var orderDetails *order.Details // find order - orderDetails, err := l.svcCtx.OrderModel.FindOneDetails(l.ctx, userSub.OrderId) - if err != nil { - l.Errorw("FindOneDetails failed:", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindOneDetails failed: %v", err.Error()) + if userSub.OrderId != 0 { + orderDetails, err = l.svcCtx.OrderModel.FindOneDetails(l.ctx, userSub.OrderId) + if err != nil { + l.Errorw("FindOneDetails failed:", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindOneDetails failed: %v", err.Error()) + } + } else { + // if order id is 0, this a admin create user subscribe + orderDetails = &order.Details{} } + userSub.Token = uuidx.SubscribeToken(orderDetails.OrderNo + time.Now().Format("20060102150405.000")) userSub.UUID = uuid.New().String() var newSub user.Subscribe @@ -63,5 +73,16 @@ func (l *ResetUserSubscribeTokenLogic) ResetUserSubscribeToken(req *types.ResetU 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 } diff --git a/internal/logic/public/user/unbindDeviceLogic.go b/internal/logic/public/user/unbindDeviceLogic.go new file mode 100644 index 0000000..e081871 --- /dev/null +++ b/internal/logic/public/user/unbindDeviceLogic.go @@ -0,0 +1,42 @@ +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/xerr" + "github.com/pkg/errors" +) + +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.UserModel.DeleteDevice(l.ctx, req.Id) +} diff --git a/internal/logic/public/user/unbindOAuthLogic.go b/internal/logic/public/user/unbindOAuthLogic.go index 97887a8..efe0ba1 100644 --- a/internal/logic/public/user/unbindOAuthLogic.go +++ b/internal/logic/public/user/unbindOAuthLogic.go @@ -3,13 +3,13 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/xerr" "github.com/pkg/errors" ) @@ -42,6 +42,7 @@ func (l *UnbindOAuthLogic) UnbindOAuth(req *types.UnbindOAuthRequest) error { l.Errorw("delete user auth methods failed:", logger.Field("error", err.Error())) return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete user auth methods failed: %v", err.Error()) } + return nil } func (l *UnbindOAuthLogic) validator(req *types.UnbindOAuthRequest) bool { diff --git a/internal/logic/public/user/unbindTelegramLogic.go b/internal/logic/public/user/unbindTelegramLogic.go index e379614..a02196f 100644 --- a/internal/logic/public/user/unbindTelegramLogic.go +++ b/internal/logic/public/user/unbindTelegramLogic.go @@ -5,15 +5,15 @@ import ( "strconv" "time" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" - "github.com/perfect-panel/ppanel-server/internal/logic/telegram" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/logic/telegram" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) diff --git a/internal/logic/public/user/unsubscribeLogic.go b/internal/logic/public/user/unsubscribeLogic.go index 0befd5d..d3390fe 100644 --- a/internal/logic/public/user/unsubscribeLogic.go +++ b/internal/logic/public/user/unsubscribeLogic.go @@ -2,17 +2,20 @@ package user import ( "context" + "time" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/pkg/constant" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" - "github.com/perfect-panel/ppanel-server/internal/model/user" + "github.com/perfect-panel/server/internal/model/user" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type UnsubscribeLogic struct { @@ -21,7 +24,7 @@ type UnsubscribeLogic struct { svcCtx *svc.ServiceContext } -// NewUnsubscribeLogic Unsubscribe +// NewUnsubscribeLogic creates a new instance of UnsubscribeLogic for handling subscription cancellation func NewUnsubscribeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UnsubscribeLogic { return &UnsubscribeLogic{ Logger: logger.WithContext(ctx), @@ -30,42 +33,135 @@ func NewUnsubscribeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Unsub } } +// Unsubscribe handles the subscription cancellation process with proper refund distribution +// It prioritizes refunding to gift amount for balance-paid orders, then to regular balance func (l *UnsubscribeLogic) Unsubscribe(req *types.UnsubscribeRequest) 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") } + + // find user subscription by ID + userSub, err := l.svcCtx.UserModel.FindOneSubscribe(l.ctx, req.Id) + if err != nil { + l.Errorw("FindOneSubscribe failed", logger.Field("error", err.Error()), logger.Field("reqId", req.Id)) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindOneSubscribe failed: %v", err.Error()) + } + + activate := []uint8{0, 1, 2} + + if !tool.Contains(activate, userSub.Status) { + // Only active (2) or paused (5) subscriptions can be cancelled + l.Errorw("Subscription status invalid for cancellation", logger.Field("userSubscribeId", userSub.Id), logger.Field("status", userSub.Status)) + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Subscription status invalid for cancellation") + } + + // Calculate the remaining amount to refund based on unused subscription time/traffic remainingAmount, err := CalculateRemainingAmount(l.ctx, l.svcCtx, req.Id) if err != nil { return err } - // update user subscribe + + // Process unsubscription in a database transaction to ensure data consistency err = l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { - var userSub user.Subscribe - if err := db.Model(&user.Subscribe{}).Where("id = ?", req.Id).First(&userSub).Error; err != nil { + // Find and update subscription status to cancelled (status = 4) + userSub.Status = 4 // Set status to cancelled + if err = l.svcCtx.UserModel.UpdateSubscribe(l.ctx, userSub); err != nil { return err } - userSub.Status = 4 - if err := l.svcCtx.UserModel.UpdateSubscribe(l.ctx, &userSub); err != nil { + + // Query the original order information to determine refund strategy + orderInfo, err := l.svcCtx.OrderModel.FindOne(l.ctx, userSub.OrderId) + if err != nil { return err } - balance := remainingAmount + u.Balance - // insert deduction log - balanceLog := user.BalanceLog{ - UserId: userSub.UserId, - OrderId: userSub.OrderId, - Amount: remainingAmount, - Type: 4, - Balance: balance, + // Calculate refund distribution based on payment method and gift amount priority + var balance, gift int64 + if orderInfo.Method == "balance" { + // For balance-paid orders, prioritize refunding to gift amount first + if orderInfo.GiftAmount >= remainingAmount { + // Gift amount covers the entire refund - refund all to gift balance + gift = remainingAmount + balance = u.Balance // Regular balance remains unchanged + } else { + // Gift amount insufficient - refund to gift first, remainder to regular balance + gift = orderInfo.GiftAmount + balance = u.Balance + (remainingAmount - orderInfo.GiftAmount) + } + } else { + // For non-balance payment orders, refund entirely to regular balance + balance = remainingAmount + u.Balance + gift = 0 } - if err := db.Model(&user.BalanceLog{}).Create(&balanceLog).Error; err != nil { - return err + + // Create balance log entry only if there's an actual regular balance refund + balanceRefundAmount := balance - u.Balance + if balanceRefundAmount > 0 { + balanceLog := log.Balance{ + OrderNo: orderInfo.OrderNo, + Amount: balanceRefundAmount, + Type: log.BalanceTypeRefund, // Type 4 represents refund transaction + Balance: balance, + Timestamp: time.Now().UnixMilli(), + } + content, _ := balanceLog.Marshal() + + if err := db.Model(&log.SystemLog{}).Create(&log.SystemLog{ + Type: log.TypeBalance.Uint8(), + Date: time.Now().Format(time.DateOnly), + ObjectID: u.Id, + Content: string(content), + }).Error; err != nil { + return err + } } - // update user balance + + // Create gift amount log entry if there's a gift balance refund + if gift > 0 { + + giftLog := log.Gift{ + SubscribeId: userSub.Id, + OrderNo: orderInfo.OrderNo, + Type: log.GiftTypeIncrease, // Type 1 represents gift amount increase + Amount: gift, + Balance: u.GiftAmount + gift, + Remark: "Unsubscribe refund", + } + content, _ := giftLog.Marshal() + + if err := db.Model(&log.SystemLog{}).Create(&log.SystemLog{ + Type: log.TypeGift.Uint8(), + Date: time.Now().Format(time.DateOnly), + ObjectID: u.Id, + Content: string(content), + }).Error; err != nil { + return err + } + // Update user's gift amount + u.GiftAmount += gift + } + + // Update user's regular balance and save changes to database u.Balance = balance return l.svcCtx.UserModel.Update(l.ctx, u) }) + if err != nil { + l.Errorw("Unsubscribe transaction failed", logger.Field("error", err.Error()), logger.Field("userId", u.Id), logger.Field("reqId", req.Id)) + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unsubscribe transaction failed: %v", err.Error()) + } + + //clear user subscription cache + if err = l.svcCtx.UserModel.ClearSubscribeCache(l.ctx, userSub); 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 err } diff --git a/internal/logic/public/user/updateBindEmailLogic.go b/internal/logic/public/user/updateBindEmailLogic.go index 3640f90..f56ff8c 100644 --- a/internal/logic/public/user/updateBindEmailLogic.go +++ b/internal/logic/public/user/updateBindEmailLogic.go @@ -3,16 +3,16 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type UpdateBindEmailLogic struct { @@ -48,7 +48,7 @@ func (l *UpdateBindEmailLogic) UpdateBindEmail(req *types.UpdateBindEmailRequest if m.Id > 0 { return errors.Wrapf(xerr.NewErrCode(xerr.UserExist), "email already bind") } - if errors.Is(err, gorm.ErrRecordNotFound) { + if method.Id == 0 { method = &user.AuthMethods{ UserId: u.Id, AuthType: "email", diff --git a/internal/logic/public/user/updateBindMobileLogic.go b/internal/logic/public/user/updateBindMobileLogic.go index 61e100e..c675a46 100644 --- a/internal/logic/public/user/updateBindMobileLogic.go +++ b/internal/logic/public/user/updateBindMobileLogic.go @@ -5,17 +5,17 @@ import ( "encoding/json" "fmt" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/phone" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/pkg/constant" + "github.com/perfect-panel/server/pkg/phone" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type UpdateBindMobileLogic struct { diff --git a/internal/logic/public/user/updateUserNotifyLogic.go b/internal/logic/public/user/updateUserNotifyLogic.go index 49ca2e6..cc9429e 100644 --- a/internal/logic/public/user/updateUserNotifyLogic.go +++ b/internal/logic/public/user/updateUserNotifyLogic.go @@ -3,13 +3,13 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/xerr" "github.com/pkg/errors" ) diff --git a/internal/logic/public/user/updateUserPasswordLogic.go b/internal/logic/public/user/updateUserPasswordLogic.go index 265f95e..eda10e7 100644 --- a/internal/logic/public/user/updateUserPasswordLogic.go +++ b/internal/logic/public/user/updateUserPasswordLogic.go @@ -3,16 +3,16 @@ package user import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type UpdateUserPasswordLogic struct { diff --git a/internal/logic/public/user/verifyEmailLogic.go b/internal/logic/public/user/verifyEmailLogic.go index 1831c84..4d48df1 100644 --- a/internal/logic/public/user/verifyEmailLogic.go +++ b/internal/logic/public/user/verifyEmailLogic.go @@ -5,13 +5,13 @@ import ( "encoding/json" "fmt" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) diff --git a/internal/logic/server/constant.go b/internal/logic/server/constant.go index 0c54202..e2d1584 100644 --- a/internal/logic/server/constant.go +++ b/internal/logic/server/constant.go @@ -1,3 +1,86 @@ package server -const Unchanged = "Unchanged" +const ( + Unchanged = "Unchanged" + ShadowSocks = "shadowsocks" + Vmess = "vmess" + Vless = "vless" + Trojan = "trojan" + AnyTLS = "anytls" + Tuic = "tuic" + Hysteria = "hysteria" + // Deprecated: Hysteria2 is deprecated, use Hysteria instead + // TODO: remove in future versions + Hysteria2 = "hysteria2" +) + +type SecurityConfig struct { + SNI string `json:"sni"` + AllowInsecure *bool `json:"allow_insecure"` + Fingerprint string `json:"fingerprint"` + RealityServerAddress string `json:"reality_server_addr"` + RealityServerPort int `json:"reality_server_port"` + RealityPrivateKey string `json:"reality_private_key"` + RealityPublicKey string `json:"reality_public_key"` + RealityShortId string `json:"reality_short_id"` + RealityMldsa65seed string `json:"reality_mldsa65seed"` +} + +type TransportConfig struct { + Path string `json:"path"` + Host string `json:"host"` + ServiceName string `json:"service_name"` + DisableSNI bool `json:"disable_sni"` + ReduceRtt bool `json:"reduce_rtt"` + UDPRelayMode string `json:"udp_relay_mode"` + CongestionController string `json:"congestion_controller"` +} + +type VlessNode struct { + Port uint16 `json:"port"` + Flow string `json:"flow"` + Network string `json:"transport"` + TransportConfig *TransportConfig `json:"transport_config"` + Security string `json:"security"` + SecurityConfig *SecurityConfig `json:"security_config"` +} + +type VmessNode struct { + Port uint16 `json:"port"` + Network string `json:"transport"` + TransportConfig *TransportConfig `json:"transport_config"` + Security string `json:"security"` + SecurityConfig *SecurityConfig `json:"security_config"` +} + +type ShadowsocksNode struct { + Port uint16 `json:"port"` + Cipher string `json:"method"` + ServerKey string `json:"server_key"` +} + +type TrojanNode struct { + Port uint16 `json:"port"` + Network string `json:"transport"` + TransportConfig *TransportConfig `json:"transport_config"` + Security string `json:"security"` + SecurityConfig *SecurityConfig `json:"security_config"` +} + +type AnyTLSNode struct { + Port uint16 `json:"port"` + SecurityConfig *SecurityConfig `json:"security_config"` +} + +type TuicNode struct { + Port uint16 `json:"port"` + SecurityConfig *SecurityConfig `json:"security_config"` +} + +type Hysteria2Node struct { + Port uint16 `json:"port"` + HopPorts string `json:"hop_ports"` + HopInterval int `json:"hop_interval"` + ObfsPassword string `json:"obfs_password"` + SecurityConfig *SecurityConfig `json:"security_config"` +} diff --git a/internal/logic/server/getServerConfigLogic.go b/internal/logic/server/getServerConfigLogic.go index 1b57ee7..94221a9 100644 --- a/internal/logic/server/getServerConfigLogic.go +++ b/internal/logic/server/getServerConfigLogic.go @@ -1,17 +1,18 @@ package server import ( + "encoding/base64" "encoding/json" "fmt" "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/model/node" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) type GetServerConfigLogic struct { @@ -20,7 +21,7 @@ type GetServerConfigLogic struct { svcCtx *svc.ServiceContext } -// Get server config +// NewGetServerConfigLogic Get server config func NewGetServerConfigLogic(ctx *gin.Context, svcCtx *svc.ServiceContext) *GetServerConfigLogic { return &GetServerConfigLogic{ Logger: logger.WithContext(ctx.Request.Context()), @@ -30,7 +31,7 @@ func NewGetServerConfigLogic(ctx *gin.Context, svcCtx *svc.ServiceContext) *GetS } func (l *GetServerConfigLogic) GetServerConfig(req *types.GetServerConfigRequest) (resp *types.GetServerConfigResponse, err error) { - cacheKey := fmt.Sprintf("%s%d", config.ServerConfigCacheKey, req.ServerId) + cacheKey := fmt.Sprintf("%s%d:%s", node.ServerConfigCacheKey, req.ServerId, req.Protocol) cache, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result() if err == nil { if cache != "" { @@ -41,7 +42,7 @@ func (l *GetServerConfigLogic) GetServerConfig(req *types.GetServerConfigRequest return nil, xerr.StatusNotModified } l.ctx.Header("ETag", etag) - resp := &types.GetServerConfigResponse{} + resp = &types.GetServerConfigResponse{} err = json.Unmarshal([]byte(cache), resp) if err != nil { l.Errorw("[ServerConfigCacheKey] json unmarshal error", logger.Field("error", err.Error())) @@ -50,34 +51,191 @@ func (l *GetServerConfigLogic) GetServerConfig(req *types.GetServerConfigRequest return resp, nil } } - nodeInfo, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.ServerId) + data, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId) if err != nil { l.Errorw("[GetServerConfig] FindOne error", logger.Field("error", err.Error())) return nil, err } - cfg := make(map[string]interface{}) - err = json.Unmarshal([]byte(nodeInfo.Config), &cfg) + + // compatible hysteria2, remove in future versions + protocolRequest := req.Protocol + if protocolRequest == Hysteria2 { + protocolRequest = Hysteria + } + + protocols, err := data.UnmarshalProtocols() if err != nil { - l.Errorw("[GetServerConfig] json unmarshal error", logger.Field("error", err.Error())) return nil, err } + var cfg map[string]interface{} + for _, protocol := range protocols { + if protocol.Type == protocolRequest { + cfg = l.compatible(protocol) + break + } + } + resp = &types.GetServerConfigResponse{ Basic: types.ServerBasic{ PullInterval: l.svcCtx.Config.Node.NodePullInterval, PushInterval: l.svcCtx.Config.Node.NodePushInterval, }, - Protocol: nodeInfo.Protocol, + Protocol: req.Protocol, Config: cfg, } - data, err := json.Marshal(resp) + c, err := json.Marshal(resp) if err != nil { l.Errorw("[GetServerConfig] json marshal error", logger.Field("error", err.Error())) return nil, err } - etag := tool.GenerateETag(data) + etag := tool.GenerateETag(c) l.ctx.Header("ETag", etag) - if err = l.svcCtx.Redis.Set(l.ctx, cacheKey, data, -1).Err(); err != nil { + if err = l.svcCtx.Redis.Set(l.ctx, cacheKey, c, -1).Err(); err != nil { l.Errorw("[GetServerConfig] redis set error", logger.Field("error", err.Error())) } + // Check If-None-Match header + match := l.ctx.GetHeader("If-None-Match") + if match == etag { + return nil, xerr.StatusNotModified + } + return resp, nil } + +func (l *GetServerConfigLogic) compatible(config node.Protocol) map[string]interface{} { + var result interface{} + switch config.Type { + case ShadowSocks: + result = ShadowsocksNode{ + Port: config.Port, + Cipher: config.Cipher, + ServerKey: base64.StdEncoding.EncodeToString([]byte(config.ServerKey)), + } + case Vless: + result = VlessNode{ + Port: config.Port, + Flow: config.Flow, + Network: config.Transport, + TransportConfig: &TransportConfig{ + Path: config.Path, + Host: config.Host, + ServiceName: config.ServiceName, + DisableSNI: config.DisableSNI, + ReduceRtt: config.ReduceRtt, + UDPRelayMode: config.UDPRelayMode, + CongestionController: config.CongestionController, + }, + Security: config.Security, + SecurityConfig: &SecurityConfig{ + SNI: config.SNI, + AllowInsecure: &config.AllowInsecure, + Fingerprint: config.Fingerprint, + RealityServerAddress: config.RealityServerAddr, + RealityServerPort: config.RealityServerPort, + RealityPrivateKey: config.RealityPrivateKey, + RealityPublicKey: config.RealityPublicKey, + RealityShortId: config.RealityShortId, + }, + } + case Vmess: + result = VmessNode{ + Port: config.Port, + Network: config.Transport, + TransportConfig: &TransportConfig{ + Path: config.Path, + Host: config.Host, + ServiceName: config.ServiceName, + DisableSNI: config.DisableSNI, + ReduceRtt: config.ReduceRtt, + UDPRelayMode: config.UDPRelayMode, + CongestionController: config.CongestionController, + }, + Security: config.Security, + SecurityConfig: &SecurityConfig{ + SNI: config.SNI, + AllowInsecure: &config.AllowInsecure, + Fingerprint: config.Fingerprint, + RealityServerAddress: config.RealityServerAddr, + RealityServerPort: config.RealityServerPort, + RealityPrivateKey: config.RealityPrivateKey, + RealityPublicKey: config.RealityPublicKey, + RealityShortId: config.RealityShortId, + }, + } + case Trojan: + result = TrojanNode{ + Port: config.Port, + Network: config.Transport, + TransportConfig: &TransportConfig{ + Path: config.Path, + Host: config.Host, + ServiceName: config.ServiceName, + DisableSNI: config.DisableSNI, + ReduceRtt: config.ReduceRtt, + UDPRelayMode: config.UDPRelayMode, + CongestionController: config.CongestionController, + }, + Security: config.Security, + SecurityConfig: &SecurityConfig{ + SNI: config.SNI, + AllowInsecure: &config.AllowInsecure, + Fingerprint: config.Fingerprint, + RealityServerAddress: config.RealityServerAddr, + RealityServerPort: config.RealityServerPort, + RealityPrivateKey: config.RealityPrivateKey, + RealityPublicKey: config.RealityPublicKey, + RealityShortId: config.RealityShortId, + }, + } + case AnyTLS: + result = AnyTLSNode{ + Port: config.Port, + SecurityConfig: &SecurityConfig{ + SNI: config.SNI, + AllowInsecure: &config.AllowInsecure, + Fingerprint: config.Fingerprint, + RealityServerAddress: config.RealityServerAddr, + RealityServerPort: config.RealityServerPort, + RealityPrivateKey: config.RealityPrivateKey, + RealityPublicKey: config.RealityPublicKey, + RealityShortId: config.RealityShortId, + }, + } + case Tuic: + result = TuicNode{ + Port: config.Port, + SecurityConfig: &SecurityConfig{ + SNI: config.SNI, + AllowInsecure: &config.AllowInsecure, + Fingerprint: config.Fingerprint, + RealityServerAddress: config.RealityServerAddr, + RealityServerPort: config.RealityServerPort, + RealityPrivateKey: config.RealityPrivateKey, + RealityPublicKey: config.RealityPublicKey, + RealityShortId: config.RealityShortId, + }, + } + case Hysteria: + result = Hysteria2Node{ + Port: config.Port, + HopPorts: config.HopPorts, + HopInterval: config.HopInterval, + ObfsPassword: config.ObfsPassword, + SecurityConfig: &SecurityConfig{ + SNI: config.SNI, + AllowInsecure: &config.AllowInsecure, + Fingerprint: config.Fingerprint, + RealityServerAddress: config.RealityServerAddr, + RealityServerPort: config.RealityServerPort, + RealityPrivateKey: config.RealityPrivateKey, + RealityPublicKey: config.RealityPublicKey, + RealityShortId: config.RealityShortId, + }, + } + + } + var resp map[string]interface{} + s, _ := json.Marshal(result) + _ = json.Unmarshal(s, &resp) + return resp +} diff --git a/internal/logic/server/getServerUserListLogic.go b/internal/logic/server/getServerUserListLogic.go index 723e648..70ea51f 100644 --- a/internal/logic/server/getServerUserListLogic.go +++ b/internal/logic/server/getServerUserListLogic.go @@ -3,16 +3,18 @@ package server import ( "encoding/json" "fmt" + "strings" "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/model/node" + "github.com/perfect-panel/server/internal/model/subscribe" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/uuidx" + "github.com/perfect-panel/server/pkg/xerr" ) type GetServerUserListLogic struct { @@ -21,7 +23,7 @@ type GetServerUserListLogic struct { svcCtx *svc.ServiceContext } -// Get user list +// NewGetServerUserListLogic Get user list func NewGetServerUserListLogic(ctx *gin.Context, svcCtx *svc.ServiceContext) *GetServerUserListLogic { return &GetServerUserListLogic{ Logger: logger.WithContext(ctx.Request.Context()), @@ -31,30 +33,53 @@ func NewGetServerUserListLogic(ctx *gin.Context, svcCtx *svc.ServiceContext) *Ge } func (l *GetServerUserListLogic) GetServerUserList(req *types.GetServerUserListRequest) (resp *types.GetServerUserListResponse, err error) { - cacheKey := fmt.Sprintf("%s%d", config.ServerUserListCacheKey, req.ServerId) + cacheKey := fmt.Sprintf("%s%d", node.ServerUserListCacheKey, req.ServerId) cache, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result() - if err == nil { - if cache != "" { - etag := tool.GenerateETag([]byte(cache)) - resp := &types.GetServerUserListResponse{} - // Check If-None-Match header - if match := l.ctx.GetHeader("If-None-Match"); match == etag { - return nil, xerr.StatusNotModified - } - l.ctx.Header("ETag", etag) - err = json.Unmarshal([]byte(cache), resp) - if err != nil { - l.Errorw("[ServerUserListCacheKey] json unmarshal error", logger.Field("error", err.Error())) - return nil, err - } - return resp, nil + if cache != "" { + etag := tool.GenerateETag([]byte(cache)) + resp = &types.GetServerUserListResponse{} + // Check If-None-Match header + if match := l.ctx.GetHeader("If-None-Match"); match == etag { + return nil, xerr.StatusNotModified } + l.ctx.Header("ETag", etag) + err = json.Unmarshal([]byte(cache), resp) + if err != nil { + l.Errorw("[ServerUserListCacheKey] json unmarshal error", logger.Field("error", err.Error())) + return nil, err + } + return resp, nil } - server, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.ServerId) + server, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId) if err != nil { return nil, err } - subs, err := l.svcCtx.SubscribeModel.QuerySubscribeIdsByServerIdAndServerGroupId(l.ctx, server.Id, server.GroupId) + + _, nodes, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{ + Page: 1, + Size: 1000, + ServerId: []int64{server.Id}, + Protocol: req.Protocol, + }) + if err != nil { + l.Errorw("FilterNodeList error", logger.Field("error", err.Error())) + return nil, err + } + var nodeTag []string + var nodeIds []int64 + for _, n := range nodes { + nodeIds = append(nodeIds, n.Id) + if n.Tags != "" { + nodeTag = append(nodeTag, strings.Split(n.Tags, ",")...) + } + } + + _, subs, err := l.svcCtx.SubscribeModel.FilterList(l.ctx, &subscribe.FilterParams{ + Page: 1, + Size: 9999, + Node: nodeIds, + Tags: nodeTag, + }) if err != nil { l.Errorw("QuerySubscribeIdsByServerIdAndServerGroupId error", logger.Field("error", err.Error())) return nil, err @@ -76,16 +101,10 @@ func (l *GetServerUserListLogic) GetServerUserList(req *types.GetServerUserListR return nil, err } for _, datum := range data { - speedLimit := server.SpeedLimit - if (int(sub.SpeedLimit) < server.SpeedLimit && sub.SpeedLimit != 0) || - (int(sub.SpeedLimit) > server.SpeedLimit && sub.SpeedLimit == 0) { - speedLimit = int(sub.SpeedLimit) - } - users = append(users, types.ServerUser{ Id: datum.Id, UUID: datum.UUID, - SpeedLimit: int64(speedLimit), + SpeedLimit: sub.SpeedLimit, DeviceLimit: sub.DeviceLimit, }) } @@ -106,5 +125,9 @@ func (l *GetServerUserListLogic) GetServerUserList(req *types.GetServerUserListR if err != nil { l.Errorw("[ServerUserListCacheKey] redis set error", logger.Field("error", err.Error())) } + // Check If-None-Match header + if match := l.ctx.GetHeader("If-None-Match"); match == etag { + return nil, xerr.StatusNotModified + } return resp, nil } diff --git a/internal/logic/server/pushOnlineUsersLogic.go b/internal/logic/server/pushOnlineUsersLogic.go index acdb942..5b17656 100644 --- a/internal/logic/server/pushOnlineUsersLogic.go +++ b/internal/logic/server/pushOnlineUsersLogic.go @@ -5,10 +5,10 @@ import ( "errors" "fmt" - "github.com/perfect-panel/ppanel-server/internal/model/cache" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/model/node" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type PushOnlineUsersLogic struct { @@ -40,26 +40,30 @@ func (l *PushOnlineUsersLogic) PushOnlineUsers(req *types.OnlineUsersRequest) er } // Find server info - _, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.ServerId) + _, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId) if err != nil { l.Errorw("[PushOnlineUsers] FindOne error", logger.Field("error", err)) return fmt.Errorf("server not found: %w", err) } - userOnlineIp := make([]cache.NodeOnlineUser, 0) + onlineUsers := make(node.OnlineUserSubscribe) for _, user := range req.Users { - userOnlineIp = append(userOnlineIp, cache.NodeOnlineUser{ - SID: user.SID, - IP: user.IP, - }) + if online, ok := onlineUsers[user.SID]; ok { + // If user already exists, update IP if different + online = append(online, user.IP) + onlineUsers[user.SID] = online + } else { + // New user, add to map + onlineUsers[user.SID] = []string{user.IP} + } } - err = l.svcCtx.NodeCache.AddOnlineUserIP(l.ctx, userOnlineIp) + err = l.svcCtx.NodeModel.UpdateOnlineUserSubscribe(l.ctx, req.ServerId, req.Protocol, onlineUsers) if err != nil { l.Errorw("[PushOnlineUsers] cache operation error", logger.Field("error", err)) return err } - err = l.svcCtx.NodeCache.UpdateNodeOnlineUser(l.ctx, req.ServerId, userOnlineIp) + err = l.svcCtx.NodeModel.UpdateOnlineUserSubscribeGlobal(l.ctx, onlineUsers) if err != nil { l.Errorw("[PushOnlineUsers] cache operation error", logger.Field("error", err)) diff --git a/internal/logic/server/queryServerProtocolConfigLogic.go b/internal/logic/server/queryServerProtocolConfigLogic.go new file mode 100644 index 0000000..4521c55 --- /dev/null +++ b/internal/logic/server/queryServerProtocolConfigLogic.go @@ -0,0 +1,93 @@ +package server + +import ( + "context" + + "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" +) + +type QueryServerProtocolConfigLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewQueryServerProtocolConfigLogic Get Server Protocol Config +func NewQueryServerProtocolConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryServerProtocolConfigLogic { + return &QueryServerProtocolConfigLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QueryServerProtocolConfigLogic) QueryServerProtocolConfig(req *types.QueryServerConfigRequest) (resp *types.QueryServerConfigResponse, err error) { + // find server + data, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerID) + if err != nil { + l.Errorf("[GetServerProtocols] FindOneServer Error: %s", err.Error()) + return nil, err + } + + // handler protocols + var protocols []types.Protocol + dst, err := data.UnmarshalProtocols() + if err != nil { + l.Errorf("[FilterServerList] UnmarshalProtocols Error: %s", err.Error()) + return nil, err + } + tool.DeepCopy(&protocols, dst) + + // filter by req.Protocols + + if len(req.Protocols) > 0 { + var filtered []types.Protocol + protocolSet := make(map[string]struct{}) + for _, p := range req.Protocols { + protocolSet[p] = struct{}{} + } + for _, p := range protocols { + if _, exists := protocolSet[p.Type]; exists { + filtered = append(filtered, p) + } + } + protocols = filtered + } + + var dns []types.NodeDNS + if len(l.svcCtx.Config.Node.DNS) > 0 { + for _, d := range l.svcCtx.Config.Node.DNS { + dns = append(dns, types.NodeDNS{ + Proto: d.Proto, + Address: d.Address, + Domains: d.Domains, + }) + } + } + var outbound []types.NodeOutbound + if len(l.svcCtx.Config.Node.Outbound) > 0 { + for _, o := range l.svcCtx.Config.Node.Outbound { + outbound = append(outbound, types.NodeOutbound{ + Name: o.Name, + Protocol: o.Protocol, + Address: o.Address, + Port: o.Port, + Password: o.Password, + Rules: o.Rules, + }) + } + } + + return &types.QueryServerConfigResponse{ + TrafficReportThreshold: l.svcCtx.Config.Node.TrafficReportThreshold, + IPStrategy: l.svcCtx.Config.Node.IPStrategy, + DNS: dns, + Block: l.svcCtx.Config.Node.Block, + Outbound: outbound, + Protocols: protocols, + Total: int64(len(protocols)), + }, nil +} diff --git a/internal/logic/server/serverPushStatusLogic.go b/internal/logic/server/serverPushStatusLogic.go index e1563d8..d7c0bad 100644 --- a/internal/logic/server/serverPushStatusLogic.go +++ b/internal/logic/server/serverPushStatusLogic.go @@ -3,11 +3,12 @@ package server import ( "context" "errors" + "time" - "github.com/perfect-panel/ppanel-server/internal/model/cache" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/model/node" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" ) type ServerPushStatusLogic struct { @@ -16,7 +17,7 @@ type ServerPushStatusLogic struct { svcCtx *svc.ServiceContext } -// Push server status +// NewServerPushStatusLogic Push server status func NewServerPushStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ServerPushStatusLogic { return &ServerPushStatusLogic{ Logger: logger.WithContext(ctx), @@ -27,12 +28,12 @@ func NewServerPushStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) * func (l *ServerPushStatusLogic) ServerPushStatus(req *types.ServerPushStatusRequest) error { // Find server info - serverInfo, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.ServerId) + serverInfo, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId) if err != nil || serverInfo.Id <= 0 { l.Errorw("[PushOnlineUsers] FindOne error", logger.Field("error", err)) return errors.New("server not found") } - err = l.svcCtx.NodeCache.UpdateNodeStatus(l.ctx, req.ServerId, cache.NodeStatus{ + err = l.svcCtx.NodeModel.UpdateStatusCache(l.ctx, req.ServerId, &node.Status{ Cpu: req.Cpu, Mem: req.Mem, Disk: req.Disk, @@ -42,5 +43,14 @@ func (l *ServerPushStatusLogic) ServerPushStatus(req *types.ServerPushStatusRequ l.Errorw("[ServerPushStatus] UpdateNodeStatus error", logger.Field("error", err)) return errors.New("update node status failed") } + now := time.Now() + serverInfo.LastReportedAt = &now + + err = l.svcCtx.NodeModel.UpdateServer(l.ctx, serverInfo) + if err != nil { + l.Errorw("[ServerPushStatus] UpdateServer error", logger.Field("error", err)) + return nil + } + return nil } diff --git a/internal/logic/server/serverPushUserTrafficLogic.go b/internal/logic/server/serverPushUserTrafficLogic.go index 4b9461a..c6ab4e6 100644 --- a/internal/logic/server/serverPushUserTrafficLogic.go +++ b/internal/logic/server/serverPushUserTrafficLogic.go @@ -3,14 +3,14 @@ package server import ( "context" "encoding/json" + "time" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/cache" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - task "github.com/perfect-panel/ppanel-server/queue/types" + "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" + task "github.com/perfect-panel/server/queue/types" "github.com/pkg/errors" ) @@ -32,7 +32,7 @@ func NewServerPushUserTrafficLogic(ctx context.Context, svcCtx *svc.ServiceConte func (l *ServerPushUserTrafficLogic) ServerPushUserTraffic(req *types.ServerPushUserTrafficRequest) error { // Find server info - serverInfo, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.ServerId) + serverInfo, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId) if err != nil { l.Errorw("[PushOnlineUsers] FindOne error", logger.Field("error", err)) return errors.New("server not found") @@ -40,23 +40,10 @@ func (l *ServerPushUserTrafficLogic) ServerPushUserTraffic(req *types.ServerPush // Create traffic task var request task.TrafficStatistics - var userTraffic []cache.UserTraffic request.ServerId = serverInfo.Id + request.Protocol = req.Protocol tool.DeepCopy(&request.Logs, req.Traffic) - tool.DeepCopy(&userTraffic, req.Traffic) - // update today traffic rank - err = l.svcCtx.NodeCache.AddNodeTodayTraffic(l.ctx, serverInfo.Id, userTraffic) - if err != nil { - l.Errorw("[ServerPushUserTraffic] AddNodeTodayTraffic error", logger.Field("error", err)) - return errors.New("add node today traffic error") - } - for _, user := range req.Traffic { - if err = l.svcCtx.NodeCache.AddUserTodayTraffic(l.ctx, user.SID, user.Upload, user.Download); err != nil { - l.Errorw("[ServerPushUserTraffic] AddUserTodayTraffic error", logger.Field("error", err)) - continue - } - } // Push traffic task val, _ := json.Marshal(request) t := asynq.NewTask(task.ForthwithTrafficStatistics, val, asynq.MaxRetry(3)) @@ -66,5 +53,15 @@ func (l *ServerPushUserTrafficLogic) ServerPushUserTraffic(req *types.ServerPush } else { l.Infow("[ServerPushUserTraffic] Push traffic task success", logger.Field("task", t), logger.Field("info", info)) } + + // Update server last reported time + now := time.Now() + serverInfo.LastReportedAt = &now + + err = l.svcCtx.NodeModel.UpdateServer(l.ctx, serverInfo) + if err != nil { + l.Errorw("[ServerPushUserTraffic] UpdateServer error", logger.Field("error", err)) + return nil + } return nil } diff --git a/internal/logic/subscribe/subscribeLogic.go b/internal/logic/subscribe/subscribeLogic.go index 36bb348..6a4a6e5 100644 --- a/internal/logic/subscribe/subscribeLogic.go +++ b/internal/logic/subscribe/subscribeLogic.go @@ -2,23 +2,23 @@ package subscribe import ( "fmt" + "net/url" "strings" "time" - "github.com/perfect-panel/ppanel-server/pkg/adapter" - "github.com/perfect-panel/ppanel-server/pkg/adapter/shadowrocket" - "github.com/perfect-panel/ppanel-server/pkg/adapter/surfboard" + "github.com/perfect-panel/server/adapter" + "github.com/perfect-panel/server/internal/model/client" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/internal/model/node" - "github.com/perfect-panel/ppanel-server/internal/model/server" - - "github.com/perfect-panel/ppanel-server/internal/model/user" + "github.com/perfect-panel/server/internal/model/user" "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" ) @@ -37,37 +37,123 @@ func NewSubscribeLogic(ctx *gin.Context, svc *svc.ServiceContext) *SubscribeLogi } } -func (l *SubscribeLogic) Generate(req *types.SubscribeRequest) (*types.SubscribeResponse, error) { - userSub, err := l.getUserSubscribe(req.Token) +func (l *SubscribeLogic) Handler(req *types.SubscribeRequest) (resp *types.SubscribeResponse, err error) { + // query client list + clients, err := l.svc.ClientModel.List(l.ctx.Request.Context()) if err != nil { + l.Errorw("[SubscribeLogic] Query client list failed", logger.Field("error", err.Error())) + return nil, err + } + + userAgent := strings.ToLower(l.ctx.Request.UserAgent()) + + var targetApp, defaultApp *client.SubscribeApplication + + for _, item := range clients { + u := strings.ToLower(item.UserAgent) + if item.IsDefault { + defaultApp = item + } + + if strings.Contains(userAgent, u) { + // Special handling for Stash + if strings.Contains(userAgent, "stash") && !strings.Contains(u, "stash") { + continue + } + targetApp = item + break + } + } + if targetApp == nil { + l.Debugf("[SubscribeLogic] No matching client found", logger.Field("userAgent", userAgent)) + if defaultApp == nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "No matching client found for user agent: %s", userAgent) + } + targetApp = defaultApp + } + // Find user subscribe by token + userSubscribe, err := l.getUserSubscribe(req.Token) + if err != nil { + l.Errorw("[SubscribeLogic] Get user subscribe failed", logger.Field("error", err.Error()), logger.Field("token", req.Token)) return nil, err } var subscribeStatus = false defer func() { - l.logSubscribeActivity(subscribeStatus, userSub, req) + l.logSubscribeActivity(subscribeStatus, userSubscribe, req) }() + // find subscribe info + subscribeInfo, err := l.svc.SubscribeModel.FindOne(l.ctx.Request.Context(), userSubscribe.SubscribeId) + if err != nil { + l.Errorw("[SubscribeLogic] Find subscribe info failed", logger.Field("error", err.Error()), logger.Field("subscribeId", userSubscribe.SubscribeId)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Find subscribe info failed: %v", err.Error()) + } - servers, err := l.getServers(userSub) + // Find server list by user subscribe + servers, err := l.getServers(userSubscribe) if err != nil { return nil, err } + a := adapter.NewAdapter( + targetApp.SubscribeTemplate, + adapter.WithServers(servers), + adapter.WithSiteName(l.svc.Config.Site.SiteName), + adapter.WithSubscribeName(subscribeInfo.Name), + adapter.WithOutputFormat(targetApp.OutputFormat), + adapter.WithUserInfo(adapter.User{ + Password: userSubscribe.UUID, + ExpiredAt: userSubscribe.ExpireTime, + Download: userSubscribe.Download, + Upload: userSubscribe.Upload, + Traffic: userSubscribe.Traffic, + SubscribeURL: l.getSubscribeV2URL(req.Token), + }), + ) - rules, err := l.getRules() + // Get client config + adapterClient, err := a.Client() if err != nil { - return nil, err + l.Errorw("[SubscribeLogic] Client error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(500), "Client error: %v", err.Error()) + } + bytes, err := adapterClient.Build() + if err != nil { + l.Errorw("[SubscribeLogic] Build client config failed", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(500), "Build client config failed: %v", err.Error()) } - resp, headerInfo, err := l.buildClientConfig(req, userSub, servers, rules) - if err != nil { - return nil, err + var formats = []string{"json", "yaml", "conf"} + + for _, format := range formats { + if format == strings.ToLower(targetApp.OutputFormat) { + l.ctx.Header("content-disposition", fmt.Sprintf("attachment;filename*=UTF-8''%s.%s", url.QueryEscape(l.svc.Config.Site.SiteName), format)) + l.ctx.Header("Content-Type", "application/octet-stream; charset=UTF-8") + + } } + resp = &types.SubscribeResponse{ + Config: bytes, + Header: fmt.Sprintf( + "upload=%d;download=%d;total=%d;expire=%d", + userSubscribe.Upload, userSubscribe.Download, userSubscribe.Traffic, userSubscribe.ExpireTime.Unix(), + ), + } subscribeStatus = true - return &types.SubscribeResponse{ - Config: resp, - Header: headerInfo, - }, nil + return +} + +func (l *SubscribeLogic) getSubscribeV2URL(token string) string { + if l.svc.Config.Subscribe.PanDomain { + return fmt.Sprintf("https://%s", l.ctx.Request.Host) + } + + if l.svc.Config.Subscribe.SubscribeDomain != "" { + domains := strings.Split(l.svc.Config.Subscribe.SubscribeDomain, "\n") + return fmt.Sprintf("https://%s%s?token=%s", domains[0], l.svc.Config.Subscribe.SubscribePath, token) + } + + return fmt.Sprintf("https://%s%s?token=%s&", l.ctx.Request.Host, l.svc.Config.Subscribe.SubscribePath, token) } func (l *SubscribeLogic) getUserSubscribe(token string) (*user.Subscribe, error) { @@ -77,10 +163,11 @@ func (l *SubscribeLogic) getUserSubscribe(token string) (*user.Subscribe, error) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe error: %v", err.Error()) } - 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") - } + // 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 } @@ -90,19 +177,27 @@ func (l *SubscribeLogic) logSubscribeActivity(subscribeStatus bool, userSub *use return } - err := l.svc.UserModel.InsertSubscribeLog(l.ctx.Request.Context(), &user.SubscribeLog{ - UserId: userSub.UserId, - UserSubscribeId: userSub.Id, + subscribeLog := log.Subscribe{ Token: req.Token, - IP: l.ctx.ClientIP(), - UserAgent: l.ctx.Request.UserAgent(), + UserAgent: req.UA, + ClientIP: l.ctx.ClientIP(), + UserSubscribeId: userSub.Id, + } + + content, _ := subscribeLog.Marshal() + + err := l.svc.LogModel.Insert(l.ctx.Request.Context(), &log.SystemLog{ + Type: log.TypeSubscribe.Uint8(), + ObjectID: userSub.UserId, // log user id + Date: time.Now().Format(time.DateOnly), + Content: string(content), }) if err != nil { l.Errorw("[Generate Subscribe]insert subscribe log error: %v", logger.Field("error", err.Error())) } } -func (l *SubscribeLogic) getServers(userSub *user.Subscribe) ([]*server.Server, error) { +func (l *SubscribeLogic) getServers(userSub *user.Subscribe) ([]*node.Node, error) { if l.isSubscriptionExpired(userSub) { return l.createExpiredServers(), nil } @@ -113,44 +208,66 @@ func (l *SubscribeLogic) getServers(userSub *user.Subscribe) ([]*server.Server, return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe details error: %v", err.Error()) } - serverIds := tool.StringToInt64Slice(subDetails.Server) - groupIds := tool.StringToInt64Slice(subDetails.ServerGroup) + 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.svc.NodeModel.FilterNodeList(l.ctx.Request.Context(), &node.FilterNodeParams{ + Page: 1, + Size: 1000, + NodeId: nodeIds, + Tag: tool.RemoveDuplicateElements(tags...), + Preload: true, + Enabled: &enable, // Only get enabled nodes + }) + + l.Debugf("[Query Subscribe]found servers: %v", len(nodes)) - servers, err := l.svc.ServerModel.FindServerDetailByGroupIdsAndIds(l.ctx.Request.Context(), groupIds, 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()) } - - return servers, nil + logger.Debugf("[Generate Subscribe]found servers: %v", len(nodes)) + return nodes, nil } func (l *SubscribeLogic) isSubscriptionExpired(userSub *user.Subscribe) bool { return userSub.ExpireTime.Unix() < time.Now().Unix() && userSub.ExpireTime.Unix() != 0 } -func (l *SubscribeLogic) createExpiredServers() []*server.Server { +func (l *SubscribeLogic) createExpiredServers() []*node.Node { enable := true host := l.getFirstHostLine() - return []*server.Server{ + return []*node.Node{ { - Name: "Subscribe Expired", - ServerAddr: "127.0.0.1", - RelayMode: "none", - Protocol: "shadowsocks", - Config: "{\"method\":\"aes-256-gcm\",\"port\":1}", - Enable: &enable, - Sort: 0, + Name: "Subscribe Expired", + Tags: "", + Port: 18080, + Address: "127.0.0.1", + Server: &node.Server{ + Id: 1, + Name: "Subscribe Expired", + Protocols: "[{\"type\":\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]", + }, + Protocol: "shadowsocks", + Enabled: &enable, }, { - Name: host, - ServerAddr: "127.0.0.1", - RelayMode: "none", - Protocol: "shadowsocks", - Config: "{\"method\":\"aes-256-gcm\",\"port\":1}", - Enable: &enable, - Sort: 0, + Name: host, + Tags: "", + Port: 18080, + Address: "127.0.0.1", + Server: &node.Server{ + Id: 1, + Name: "Subscribe Expired", + Protocols: "[{\"type\":\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]", + }, + Protocol: "shadowsocks", + Enabled: &enable, }, } } @@ -163,127 +280,3 @@ func (l *SubscribeLogic) getFirstHostLine() string { } return host } - -func (l *SubscribeLogic) getRules() ([]*server.RuleGroup, error) { - rules, err := l.svc.ServerModel.QueryAllRuleGroup(l.ctx) - if err != nil { - l.Errorw("[Generate Subscribe]find rule group error: %v", logger.Field("error", err.Error())) - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find rule group error: %v", err.Error()) - } - return rules, nil -} - -func (l *SubscribeLogic) buildClientConfig(req *types.SubscribeRequest, userSub *user.Subscribe, servers []*server.Server, rules []*server.RuleGroup) ([]byte, string, error) { - proxyManager := adapter.NewAdapter(servers, rules) - clientType := l.getClientType(req) - var resp []byte - var err error - - l.Logger.Info(fmt.Sprintf("[Generate Subscribe] %s", clientType), logger.Field("ua", req.UA), logger.Field("flag", req.Flag)) - - switch clientType { - case "clash": - resp, err = proxyManager.BuildClash(userSub.UUID) - if err != nil { - l.Errorw("[Generate Subscribe] build clash error", logger.Field("error", err.Error())) - return nil, "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "build clash error: %v", err.Error()) - } - l.setClashHeaders() - case "sing-box": - resp, err = proxyManager.BuildSingbox(userSub.UUID) - if err != nil { - l.Errorw("[Generate Subscribe] build sing-box error", logger.Field("error", err.Error())) - return nil, "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "build sing-box error: %v", err.Error()) - } - case "quantumult": - resp = []byte(proxyManager.BuildQuantumultX(userSub.UUID)) - case "shadowrocket": - resp = proxyManager.BuildShadowrocket(userSub.UUID, shadowrocket.UserInfo{ - Upload: userSub.Upload, - Download: userSub.Download, - TotalTraffic: userSub.Traffic, - ExpiredDate: userSub.ExpireTime, - }) - case "loon": - resp = proxyManager.BuildLoon(userSub.UUID) - case "surfboard": - subsURL := l.getSubscribeURL(userSub.Token) - resp = proxyManager.BuildSurfboard(l.svc.Config.Site.SiteName, surfboard.UserInfo{ - Upload: userSub.Upload, - Download: userSub.Download, - TotalTraffic: userSub.Traffic, - ExpiredDate: userSub.ExpireTime, - UUID: userSub.UUID, - SubscribeURL: subsURL, - }) - l.setSurfboardHeaders() - default: - resp = proxyManager.BuildGeneral(userSub.UUID) - } - - headerInfo := fmt.Sprintf("upload=%d;download=%d;total=%d;expire=%d", - userSub.Upload, userSub.Download, userSub.Traffic, userSub.ExpireTime.Unix()) - - return resp, headerInfo, nil -} - -func (l *SubscribeLogic) setClashHeaders() { - l.ctx.Header("content-disposition", fmt.Sprintf("tattachment;filename*=UTF-8''%s.yaml", l.svc.Config.Site.SiteName)) - l.ctx.Header("Profile-Update-Interval", "24") - l.ctx.Header("Content-Type", "application/octet-stream; charset=UTF-8") -} - -func (l *SubscribeLogic) setSurfboardHeaders() { - l.ctx.Header("content-disposition", fmt.Sprintf("attachment;filename*=UTF-8''%s.conf", l.svc.Config.Site.SiteName)) - l.ctx.Header("Content-Type", "application/octet-stream; charset=UTF-8") -} - -func (l *SubscribeLogic) getSubscribeURL(token string) string { - if l.svc.Config.Subscribe.PanDomain { - return fmt.Sprintf("https://%s", l.ctx.Request.Host) - } - - if l.svc.Config.Subscribe.SubscribeDomain != "" { - domains := strings.Split(l.svc.Config.Subscribe.SubscribeDomain, "\n") - return fmt.Sprintf("https://%s%s?token=%s&flag=surfboard", domains[0], l.svc.Config.Subscribe.SubscribePath, token) - } - - return fmt.Sprintf("https://%s%s?token=%s&flag=surfboard", l.ctx.Request.Host, l.svc.Config.Subscribe.SubscribePath, token) -} - -func (l *SubscribeLogic) getClientType(req *types.SubscribeRequest) string { - clientTypeMap := map[string]string{ - "clash": "clash", - "meta": "clash", - "sing-box": "sing-box", - "hiddify": "sing-box", - "surge": "surge", - "quantumult": "quantumult", - "shadowrocket": "shadowrocket", - "loon": "loon", - "surfboard": "surfboard", - } - - findClient := func(s string) string { - s = strings.ToLower(strings.TrimSpace(s)) - if s == "" { - return "" - } - - for key, clientType := range clientTypeMap { - if strings.Contains(s, key) { - return clientType - } - } - - return "" - } - - // 优先检查Flag参数 - if typ := findClient(req.Flag); typ != "" { - return typ - } - - // 其次检查UA参数 - return findClient(req.UA) -} diff --git a/internal/logic/telegram/bot.go b/internal/logic/telegram/bot.go index 3dd7bd8..3ba2306 100644 --- a/internal/logic/telegram/bot.go +++ b/internal/logic/telegram/bot.go @@ -8,15 +8,15 @@ import ( "strings" "time" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/model/auth" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/model/auth" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) diff --git a/internal/logic/telegram/telegramLogic.go b/internal/logic/telegram/telegramLogic.go index 0eedc4d..bc5f9f8 100644 --- a/internal/logic/telegram/telegramLogic.go +++ b/internal/logic/telegram/telegramLogic.go @@ -7,12 +7,12 @@ import ( "time" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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/pkg/logger" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "github.com/redis/go-redis/v9" "gorm.io/gorm" diff --git a/internal/middleware/authMiddleware.go b/internal/middleware/authMiddleware.go index 8bf45a8..fbf3758 100644 --- a/internal/middleware/authMiddleware.go +++ b/internal/middleware/authMiddleware.go @@ -5,17 +5,17 @@ import ( "fmt" "strings" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/jwt" - "github.com/perfect-panel/ppanel-server/pkg/result" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/jwt" + "github.com/perfect-panel/server/pkg/result" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) @@ -40,6 +40,11 @@ func AuthMiddleware(svc *svc.ServiceContext) func(c *gin.Context) { c.Abort() return } + + loginType := "" + if claims["LoginType"] != nil { + loginType = claims["LoginType"].(string) + } // get user id from token userId := int64(claims["UserId"].(float64)) // get session id from token @@ -77,6 +82,7 @@ func AuthMiddleware(svc *svc.ServiceContext) func(c *gin.Context) { c.Abort() return } + ctx = context.WithValue(ctx, constant.LoginType, loginType) ctx = context.WithValue(ctx, constant.CtxKeyUser, userInfo) ctx = context.WithValue(ctx, constant.CtxKeySessionID, sessionId) c.Request = c.Request.WithContext(ctx) diff --git a/internal/middleware/appMiddleware.go b/internal/middleware/deviceMiddleware.go similarity index 85% rename from internal/middleware/appMiddleware.go rename to internal/middleware/deviceMiddleware.go index c1bef7e..501d4d0 100644 --- a/internal/middleware/appMiddleware.go +++ b/internal/middleware/deviceMiddleware.go @@ -3,6 +3,7 @@ package middleware import ( "bufio" "bytes" + "context" "encoding/json" "fmt" "io" @@ -10,29 +11,40 @@ import ( "net/http" "strings" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/result" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "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" - "github.com/perfect-panel/ppanel-server/internal/svc" - pkgaes "github.com/perfect-panel/ppanel-server/pkg/aes" ) const ( noWritten = -1 defaultStatus = http.StatusOK - key = "123456" ) -func AppMiddleware(svc *svc.ServiceContext) func(c *gin.Context) { +func DeviceMiddleware(srvCtx *svc.ServiceContext) func(c *gin.Context) { return func(c *gin.Context) { - if !strings.Contains(c.Request.URL.Path, "/v1/app") { + loginType := c.GetString(string(constant.LoginType)) + if loginType == "" { + loginType = c.GetHeader("Login-Type") + } + + if loginType != "device" { c.Next() return } - rw := NewResponseWriter(c, svc) + + c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), constant.LoginType, loginType)) + + if !srvCtx.Config.Device.Enable || srvCtx.Config.Device.SecuritySecret == "" { + c.Next() + return + } + rw := NewResponseWriter(c, srvCtx) if !rw.Decrypt() { result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidCiphertext), "Invalid ciphertext")) c.Abort() @@ -50,17 +62,10 @@ func NewResponseWriter(c *gin.Context, srvCtx *svc.ServiceContext) (rw *Response body: new(bytes.Buffer), ResponseWriter: c.Writer, } - applicationConfig, err := srvCtx.ApplicationModel.FindOneConfig(c, 1) - if err != nil { - logger.Errorf("[AppMiddleware] find application config error: %v", err.Error()) - return - } - if strings.ToUpper(applicationConfig.EncryptionMethod) == "AES" && applicationConfig.EncryptionKey != "" { - rw.encryptionKey = applicationConfig.EncryptionKey - rw.encryptionMethod = applicationConfig.EncryptionMethod - rw.encryption = true - } - return + rw.encryptionKey = srvCtx.Config.Device.SecuritySecret + rw.encryptionMethod = "AES" + rw.encryption = true + return rw } func (rw *ResponseWriter) Encrypt() { diff --git a/internal/middleware/loggerMiddleware.go b/internal/middleware/loggerMiddleware.go index a7faaa7..7bb2def 100644 --- a/internal/middleware/loggerMiddleware.go +++ b/internal/middleware/loggerMiddleware.go @@ -6,13 +6,13 @@ import ( "io" "time" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/svc" + "github.com/perfect-panel/server/internal/svc" ) type responseBodyWriter struct { @@ -44,6 +44,9 @@ func LoggerMiddleware(svc *svc.ServiceContext) func(c *gin.Context) { // Start recording logs cost := time.Since(start) responseStatus := c.Writer.Status() + + host := c.Request.Host + logs := []logger.LogField{ { Key: "status", @@ -51,7 +54,7 @@ func LoggerMiddleware(svc *svc.ServiceContext) func(c *gin.Context) { }, { Key: "request", - Value: c.Request.Method + " " + c.Request.URL.String(), + Value: c.Request.Method + " " + host + c.Request.URL.String(), }, { Key: "query", @@ -88,6 +91,10 @@ func LoggerMiddleware(svc *svc.ServiceContext) func(c *gin.Context) { } else { logger.WithContext(c.Request.Context()).Infow("HTTP Request", logs...) } + + if responseStatus == 404 { + logger.WithContext(c.Request.Context()).Debugf("404 Not Found: Host:%s Path:%s IsPanDomain:%v", host, c.Request.URL.Path, svc.Config.Subscribe.PanDomain) + } } } diff --git a/internal/middleware/notifyMiddleware.go b/internal/middleware/notifyMiddleware.go index ba9dc32..bbee8a9 100644 --- a/internal/middleware/notifyMiddleware.go +++ b/internal/middleware/notifyMiddleware.go @@ -3,10 +3,10 @@ package middleware import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/svc" + "github.com/perfect-panel/server/internal/svc" ) type PaymentParams struct { diff --git a/internal/middleware/panDomainMiddleware.go b/internal/middleware/panDomainMiddleware.go index 3e51bee..518c787 100644 --- a/internal/middleware/panDomainMiddleware.go +++ b/internal/middleware/panDomainMiddleware.go @@ -1,17 +1,60 @@ package middleware import ( + "net/http" "strings" "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/logic/subscribe" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" + "github.com/perfect-panel/server/internal/logic/subscribe" + "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" ) func PanDomainMiddleware(svc *svc.ServiceContext) func(c *gin.Context) { return func(c *gin.Context) { - if svc.Config.Subscribe.PanDomain { + + if svc.Config.Subscribe.PanDomain && c.Request.URL.Path == "/" { + // intercept browser + ua := c.GetHeader("User-Agent") + + if svc.Config.Subscribe.UserAgentLimit { + if ua == "" { + c.String(http.StatusForbidden, "Access denied") + c.Abort() + return + } + browserKeywords := tool.RemoveDuplicateElements(strings.Split(svc.Config.Subscribe.UserAgentList, "\n")...) + var allow = false + + // query client list + clients, err := svc.ClientModel.List(c.Request.Context()) + if err != nil { + logger.Errorw("[PanDomainMiddleware] Query client list failed", logger.Field("error", err.Error())) + } + for _, item := range clients { + u := strings.ToLower(item.UserAgent) + u = strings.Trim(u, " ") + browserKeywords = append(browserKeywords, u) + } + + for _, keyword := range browserKeywords { + keyword = strings.ToLower(strings.Trim(keyword, " ")) + if keyword == "" { + continue + } + if strings.Contains(strings.ToLower(ua), keyword) { + allow = true + } + } + if !allow { + c.String(http.StatusForbidden, "Access denied") + c.Abort() + return + } + } + domain := c.Request.Host domainArr := strings.Split(domain, ".") domainFirst := domainArr[0] @@ -21,7 +64,7 @@ func PanDomainMiddleware(svc *svc.ServiceContext) func(c *gin.Context) { UA: c.Request.Header.Get("User-Agent"), } l := subscribe.NewSubscribeLogic(c, svc) - resp, err := l.Generate(&request) + resp, err := l.Handler(&request) if err != nil { return } diff --git a/internal/middleware/serverMiddleware.go b/internal/middleware/serverMiddleware.go index 0c7c221..2114342 100644 --- a/internal/middleware/serverMiddleware.go +++ b/internal/middleware/serverMiddleware.go @@ -2,7 +2,7 @@ package middleware import ( "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/internal/svc" + "github.com/perfect-panel/server/internal/svc" ) func ServerMiddleware(svc *svc.ServiceContext) func(c *gin.Context) { diff --git a/internal/middleware/traceMiddleware.go b/internal/middleware/traceMiddleware.go index 0793041..6e143a8 100644 --- a/internal/middleware/traceMiddleware.go +++ b/internal/middleware/traceMiddleware.go @@ -6,18 +6,16 @@ import ( "net/http" "strings" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" "github.com/gin-gonic/gin" - "github.com/google/uuid" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.24.0" oteltrace "go.opentelemetry.io/otel/trace" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/trace" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/trace" ) // statusByWriter returns a span status code and message for an HTTP status code @@ -71,19 +69,13 @@ func TraceMiddleware(_ *svc.ServiceContext) func(ctx *gin.Context) { ) defer span.End() - requestId, err := uuid.NewV7() - if err != nil { - logger.Errorw( - "failed to generate request id in uuid v7 format, fallback to uuid v4", - logger.Field("error", err), - ) - requestId = uuid.New() - } - c.Header(trace.RequestIdKey, requestId.String()) + requestId := trace.TraceIDFromContext(ctx) + + c.Header(trace.RequestIdKey, requestId) span.SetAttributes(requestAttributes(c.Request)...) span.SetAttributes( - attribute.String("http.request_id", requestId.String()), + attribute.String("http.request_id", requestId), semconv.HTTPRouteKey.String(c.FullPath()), ) // context with request host diff --git a/internal/model/ads/default.go b/internal/model/ads/default.go index 52f9e99..11f5db4 100644 --- a/internal/model/ads/default.go +++ b/internal/model/ads/default.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/perfect-panel/ppanel-server/pkg/cache" + "github.com/perfect-panel/server/pkg/cache" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) diff --git a/internal/model/announcement/default.go b/internal/model/announcement/default.go index 36ccae4..80e6ab0 100644 --- a/internal/model/announcement/default.go +++ b/internal/model/announcement/default.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/perfect-panel/ppanel-server/pkg/cache" + "github.com/perfect-panel/server/pkg/cache" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) diff --git a/internal/model/application/application.go b/internal/model/application/application.go deleted file mode 100644 index 196e9e8..0000000 --- a/internal/model/application/application.go +++ /dev/null @@ -1,54 +0,0 @@ -package application - -import ( - "time" -) - -type Application struct { - Id int64 `gorm:"primary_key"` - Name string `gorm:"type:varchar(255);default:'';not null;comment:应用名称"` - Icon string `gorm:"type:text;not null;comment:应用图标"` - Description string `gorm:"type:text;comment:更新描述"` - SubscribeType string `gorm:"type:varchar(50);default:'';not null;comment:订阅类型"` - ApplicationVersions []ApplicationVersion - CreatedAt time.Time `gorm:"<-:create;comment:创建时间"` - UpdatedAt time.Time `gorm:"comment:更新时间"` -} - -func (Application) TableName() string { - return "application" -} - -type ApplicationVersion struct { - Id int64 `gorm:"primary_key"` - Url string `gorm:"type:varchar(255);default:'';not null;comment:应用地址"` - Version string `gorm:"type:varchar(255);default:'';not null;comment:应用版本"` - Platform string `gorm:"type:varchar(50);default:'';not null;comment:应用平台"` - IsDefault bool `gorm:"type:tinyint(1);not null;default:0;comment:默认版本"` - Description string `gorm:"type:text;comment:更新描述"` - ApplicationId int64 `gorm:"comment:所属应用"` - CreatedAt time.Time `gorm:"<-:create;comment:创建时间"` - UpdatedAt time.Time `gorm:"comment:更新时间"` -} - -func (ApplicationVersion) TableName() string { - return "application_version" -} - -type ApplicationConfig struct { - Id int64 `gorm:"primary_key"` - AppId int64 `gorm:"type:int;not null;default:0;comment:App id"` - EncryptionKey string `gorm:"type:text;comment:Encryption Key"` - EncryptionMethod string `gorm:"type:varchar(255);comment:Encryption Method"` - Domains string `gorm:"type:text"` - StartupPicture string `gorm:"type:text"` - StartupPictureSkipTime int64 `gorm:"type:int;not null;default:0;comment:Startup Picture Skip Time"` - InvitationLink string `gorm:"Invitation link"` - KrWebsiteId string `gorm:"type:varchar(255);default:'';comment:Kr Website ID"` - CreatedAt time.Time `gorm:"<-:create;comment:Create Time"` - UpdatedAt time.Time `gorm:"comment:Update Time"` -} - -func (ApplicationConfig) TableName() string { - return "application_config" -} diff --git a/internal/model/application/default.go b/internal/model/application/default.go deleted file mode 100644 index 857637d..0000000 --- a/internal/model/application/default.go +++ /dev/null @@ -1,245 +0,0 @@ -package application - -import ( - "context" - "errors" - "fmt" - - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/pkg/cache" - "github.com/redis/go-redis/v9" - "gorm.io/gorm" -) - -var _ Model = (*customApplicationModel)(nil) -var ( - cacheApplicationIdPrefix = "cache:application:id:" - cacheApplicationConfigIdPrefix = "cache:application:config:id:" - cacheApplicationVersionIdPrefix = "cache:application:version:id:" -) - -type ( - Model interface { - applicationModel - customApplicationLogicModel - } - applicationModel interface { - Insert(ctx context.Context, data *Application) error - FindOne(ctx context.Context, id int64) (*Application, error) - Update(ctx context.Context, data *Application) error - Delete(ctx context.Context, id int64) error - InsertVersion(ctx context.Context, data *ApplicationVersion) error - FindOneVersion(ctx context.Context, id int64) (*ApplicationVersion, error) - UpdateVersion(ctx context.Context, data *ApplicationVersion) error - InsertConfig(ctx context.Context, data *ApplicationConfig) error - FindOneConfig(ctx context.Context, id int64) (*ApplicationConfig, error) - UpdateConfig(ctx context.Context, data *ApplicationConfig) error - DeleteVersion(ctx context.Context, id int64) error - Transaction(ctx context.Context, fn func(db *gorm.DB) error) error - } - - customApplicationModel struct { - *defaultApplicationModel - } - defaultApplicationModel struct { - cache.CachedConn - table string - } -) - -func newApplicationModel(db *gorm.DB, c *redis.Client) *defaultApplicationModel { - return &defaultApplicationModel{ - CachedConn: cache.NewConn(db, c), - table: "`Application`", - } -} - -func (m *defaultApplicationModel) getCacheKeys(data *Application) []string { - if data == nil { - return []string{} - } - ApplicationIdKey := fmt.Sprintf("%s%v", cacheApplicationIdPrefix, data.Id) - cacheKeys := []string{ - ApplicationIdKey, - config.ApplicationKey, - } - return cacheKeys -} - -func (m *defaultApplicationModel) Insert(ctx context.Context, data *Application) error { - err := m.ExecCtx(ctx, func(conn *gorm.DB) error { - return conn.Create(&data).Error - }, m.getCacheKeys(data)...) - return err -} - -func (m *defaultApplicationModel) FindOne(ctx context.Context, id int64) (*Application, error) { - ApplicationIdKey := fmt.Sprintf("%s%v", cacheApplicationIdPrefix, id) - var resp Application - err := m.QueryCtx(ctx, &resp, ApplicationIdKey, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&Application{}).Preload("ApplicationVersions").Where("`id` = ?", id).First(&resp).Error - }) - switch { - case err == nil: - return &resp, nil - default: - return nil, err - } -} - -func (m *defaultApplicationModel) Update(ctx context.Context, data *Application) error { - old, err := m.FindOne(ctx, data.Id) - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return err - } - err = m.ExecCtx(ctx, func(conn *gorm.DB) error { - db := conn - return db.Save(data).Error - }, m.getCacheKeys(old)...) - return err -} - -func (m *defaultApplicationModel) Delete(ctx context.Context, id int64) error { - data, err := m.FindOne(ctx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil - } - return err - } - err = m.ExecCtx(ctx, func(conn *gorm.DB) error { - db := conn - err = db.Where("application_id = ?", id).Delete(&ApplicationVersion{}).Error - if err != nil { - return err - } - return db.Delete(&Application{}, id).Error - }, m.getCacheKeys(data)...) - return err -} - -func (m *defaultApplicationModel) getVersionCacheKeys(data *ApplicationVersion) []string { - if data == nil { - return []string{} - } - ApplicationVersionIdKey := fmt.Sprintf("%s%v", cacheApplicationVersionIdPrefix, data.Id) - cacheKeys := []string{ - ApplicationVersionIdKey, - config.ApplicationKey, - } - return cacheKeys -} -func (m *defaultApplicationModel) getConfigCacheKeys(data *ApplicationConfig) []string { - if data == nil { - return []string{} - } - ApplicationConfigIdKey := fmt.Sprintf("%s%v", cacheApplicationConfigIdPrefix, data.Id) - cacheKeys := []string{ - ApplicationConfigIdKey, - config.ApplicationKey, - } - return cacheKeys -} - -func (m *defaultApplicationModel) InsertVersion(ctx context.Context, data *ApplicationVersion) error { - err := m.ExecCtx(ctx, func(conn *gorm.DB) error { - return conn.Transaction(func(tx *gorm.DB) error { - if data.IsDefault { - err := tx.Model(&ApplicationVersion{}). - Where("application_id = ? and platform = ? and default_version = ?", data.ApplicationId, data.Platform, data.IsDefault). - Updates(map[string]interface{}{"default_version": false}).Error - if err != nil { - return err - } - } - return tx.Create(&data).Error - }) - }, m.getVersionCacheKeys(data)...) - return err -} - -func (m *defaultApplicationModel) FindOneVersion(ctx context.Context, id int64) (*ApplicationVersion, error) { - ApplicationVersionIdKey := fmt.Sprintf("%s%v", cacheApplicationVersionIdPrefix, id) - var resp ApplicationVersion - err := m.QueryCtx(ctx, &resp, ApplicationVersionIdKey, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&ApplicationVersion{}).Where("`id` = ?", id).First(&resp).Error - }) - switch { - case err == nil: - return &resp, nil - default: - return nil, err - } -} - -func (m *defaultApplicationModel) UpdateVersion(ctx context.Context, data *ApplicationVersion) error { - old, err := m.FindOneVersion(ctx, data.Id) - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return err - } - err = m.ExecCtx(ctx, func(conn *gorm.DB) error { - return conn.Transaction(func(tx *gorm.DB) error { - if data.IsDefault { - err := tx.Model(&ApplicationVersion{}). - Where("application_id = ? and platform = ? and default_version = ?", data.ApplicationId, data.Platform, data.IsDefault). - Updates(map[string]interface{}{"default_version": false}).Error - if err != nil { - return err - } - } - return tx.Save(data).Error - }) - }, m.getVersionCacheKeys(old)...) - return err -} - -func (m *defaultApplicationModel) InsertConfig(ctx context.Context, data *ApplicationConfig) error { - err := m.ExecCtx(ctx, func(conn *gorm.DB) error { - return conn.Create(&data).Error - }, m.getConfigCacheKeys(data)...) - return err -} - -func (m *defaultApplicationModel) FindOneConfig(ctx context.Context, id int64) (*ApplicationConfig, error) { - ApplicationConfigIdKey := fmt.Sprintf("%s%v", cacheApplicationConfigIdPrefix, id) - var resp ApplicationConfig - err := m.QueryCtx(ctx, &resp, ApplicationConfigIdKey, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&ApplicationConfig{}).Where("`id` = ?", id).First(&resp).Error - }) - switch { - case err == nil: - return &resp, nil - default: - return nil, err - } -} - -func (m *defaultApplicationModel) UpdateConfig(ctx context.Context, data *ApplicationConfig) error { - old, err := m.FindOneConfig(ctx, data.Id) - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return err - } - err = m.ExecCtx(ctx, func(conn *gorm.DB) error { - return conn.Save(data).Error - }, m.getConfigCacheKeys(old)...) - return err -} - -func (m *defaultApplicationModel) DeleteVersion(ctx context.Context, id int64) error { - data, err := m.FindOneVersion(ctx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil - } - return err - } - err = m.ExecCtx(ctx, func(conn *gorm.DB) error { - db := conn - return db.Delete(&ApplicationVersion{}, id).Error - }, m.getVersionCacheKeys(data)...) - return err -} - -func (m *defaultApplicationModel) Transaction(ctx context.Context, fn func(db *gorm.DB) error) error { - return m.TransactCtx(ctx, fn) -} diff --git a/internal/model/application/model.go b/internal/model/application/model.go deleted file mode 100644 index 3bd5b0a..0000000 --- a/internal/model/application/model.go +++ /dev/null @@ -1,16 +0,0 @@ -package application - -import ( - "github.com/redis/go-redis/v9" - "gorm.io/gorm" -) - -type customApplicationLogicModel interface { -} - -// NewModel returns a model for the database table. -func NewModel(conn *gorm.DB, c *redis.Client) Model { - return &customApplicationModel{ - defaultApplicationModel: newApplicationModel(conn, c), - } -} diff --git a/internal/model/auth/auth.go b/internal/model/auth/auth.go index f98d5cd..f85070c 100644 --- a/internal/model/auth/auth.go +++ b/internal/model/auth/auth.go @@ -3,6 +3,8 @@ package auth import ( "encoding/json" "time" + + "github.com/perfect-panel/server/pkg/email" ) type Auth struct { @@ -124,15 +126,55 @@ type EmailAuthConfig struct { } func (l *EmailAuthConfig) Marshal() string { + if l.ExpirationEmailTemplate == "" { + l.ExpirationEmailTemplate = email.DefaultExpirationEmailTemplate + } + if l.ExpirationEmailTemplate == "" { + l.MaintenanceEmailTemplate = email.DefaultMaintenanceEmailTemplate + } + if l.TrafficExceedEmailTemplate == "" { + l.TrafficExceedEmailTemplate = email.DefaultTrafficExceedEmailTemplate + } + if l.VerifyEmailTemplate == "" { + l.VerifyEmailTemplate = email.DefaultEmailVerifyTemplate + } bytes, err := json.Marshal(l) if err != nil { - bytes, _ = json.Marshal(new(EmailAuthConfig)) + config := &EmailAuthConfig{ + Platform: "smtp", + PlatformConfig: new(SMTPConfig), + EnableVerify: true, + EnableNotify: true, + EnableDomainSuffix: false, + DomainSuffixList: "", + VerifyEmailTemplate: email.DefaultEmailVerifyTemplate, + ExpirationEmailTemplate: email.DefaultExpirationEmailTemplate, + MaintenanceEmailTemplate: email.DefaultMaintenanceEmailTemplate, + TrafficExceedEmailTemplate: email.DefaultTrafficExceedEmailTemplate, + } + + bytes, _ = json.Marshal(config) } return string(bytes) } -func (l *EmailAuthConfig) Unmarshal(data string) error { - return json.Unmarshal([]byte(data), &l) +func (l *EmailAuthConfig) Unmarshal(data string) { + err := json.Unmarshal([]byte(data), &l) + if err != nil { + config := &EmailAuthConfig{ + Platform: "smtp", + PlatformConfig: new(SMTPConfig), + EnableVerify: true, + EnableNotify: true, + EnableDomainSuffix: false, + DomainSuffixList: "", + VerifyEmailTemplate: email.DefaultEmailVerifyTemplate, + ExpirationEmailTemplate: email.DefaultExpirationEmailTemplate, + MaintenanceEmailTemplate: email.DefaultMaintenanceEmailTemplate, + TrafficExceedEmailTemplate: email.DefaultTrafficExceedEmailTemplate, + } + _ = json.Unmarshal([]byte(config.Marshal()), &l) + } } // SMTPConfig Email SMTP configuration @@ -167,13 +209,28 @@ type MobileAuthConfig struct { func (l *MobileAuthConfig) Marshal() string { bytes, err := json.Marshal(l) if err != nil { - bytes, _ = json.Marshal(new(MobileAuthConfig)) + config := &MobileAuthConfig{ + Platform: "alibaba_cloud", + PlatformConfig: new(AlibabaCloudConfig), + EnableWhitelist: false, + Whitelist: []string{}, + } + bytes, _ = json.Marshal(config) } return string(bytes) } -func (l *MobileAuthConfig) Unmarshal(data string) error { - return json.Unmarshal([]byte(data), &l) +func (l *MobileAuthConfig) Unmarshal(data string) { + err := json.Unmarshal([]byte(data), &l) + if err != nil { + config := &MobileAuthConfig{ + Platform: "alibaba_cloud", + PlatformConfig: new(AlibabaCloudConfig), + EnableWhitelist: false, + Whitelist: []string{}, + } + _ = json.Unmarshal([]byte(config.Marshal()), &l) + } } type AlibabaCloudConfig struct { diff --git a/internal/model/auth/default.go b/internal/model/auth/default.go index 8131d78..df80ace 100644 --- a/internal/model/auth/default.go +++ b/internal/model/auth/default.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/perfect-panel/ppanel-server/pkg/cache" + "github.com/perfect-panel/server/pkg/cache" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) diff --git a/internal/model/cache/constant.go b/internal/model/cache/constant.go deleted file mode 100644 index 5ae995c..0000000 --- a/internal/model/cache/constant.go +++ /dev/null @@ -1,52 +0,0 @@ -package cache - -const ( - // UserTodayUploadTrafficCacheKey 用户当日上传流量 - UserTodayUploadTrafficCacheKey = "node:user_today_upload_traffic" - // UserTodayDownloadTrafficCacheKey 用户当日下载流量 - UserTodayDownloadTrafficCacheKey = "node:user_today_download_traffic" - // UserTodayTotalTrafficCacheKey 用户当日总流量 - UserTodayTotalTrafficCacheKey = "node:user_today_total_traffic" - // NodeTodayUploadTrafficCacheKey 节点当日上传流量 - NodeTodayUploadTrafficCacheKey = "node:node_today_upload_traffic" - // NodeTodayDownloadTrafficCacheKey 节点当日下载流量 - NodeTodayDownloadTrafficCacheKey = "node:node_today_download_traffic" - // NodeTodayTotalTrafficCacheKey 节点当日总流量 - NodeTodayTotalTrafficCacheKey = "node:node_today_total_traffic" - // UserTodayUploadTrafficRankKey 用户当日上传流量排行榜 - UserTodayUploadTrafficRankKey = "node:user_today_upload_traffic_rank" - // UserTodayDownloadTrafficRankKey 用户当日下载流量排行榜 - UserTodayDownloadTrafficRankKey = "node:user_today_download_traffic_rank" - // UserTodayTotalTrafficRankKey 用户当日总流量排行榜 - UserTodayTotalTrafficRankKey = "node:user_today_total_traffic_rank" - // NodeTodayUploadTrafficRankKey 节点当日上传流量排行榜 - NodeTodayUploadTrafficRankKey = "node:node_today_upload_traffic_rank" - // NodeTodayDownloadTrafficRankKey 节点当日下载流量排行榜 - NodeTodayDownloadTrafficRankKey = "node:node_today_download_traffic_rank" - // NodeTodayTotalTrafficRankKey 节点当日总流量排行榜 - NodeTodayTotalTrafficRankKey = "node:node_today_total_traffic_rank" - // NodeOnlineUserCacheKey 节点在线用户 - NodeOnlineUserCacheKey = "node:node_online_user:%d" - // UserOnlineIpCacheKey 用户在线IP - UserOnlineIpCacheKey = "node:user_online_ip:%d" - // AllNodeOnlineUserCacheKey 所有节点在线用户 - AllNodeOnlineUserCacheKey = "node:all_node_online_user" - // NodeStatusCacheKey 节点状态 - NodeStatusCacheKey = "node:status:%d" - // AllNodeDownloadTrafficCacheKey 所有节点下载流量 - AllNodeDownloadTrafficCacheKey = "node:all_node_download_traffic" - // AllNodeUploadTrafficCacheKey 所有节点上传流量 - AllNodeUploadTrafficCacheKey = "node:all_node_upload_traffic" - // YesterdayTotalTrafficRank 昨日节点总流量排行榜 - YesterdayNodeTotalTrafficRank = "node:yesterday_total_traffic_rank" - // YesterdayUploadTrafficRank 昨日节点上传流量排行榜 - YesterdayNodeUploadTrafficRank = "node:yesterday_upload_traffic_rank" - // YesterdayDownloadTrafficRank 昨日节点下载流量排行榜 - YesterdayNodeDownloadTrafficRank = "node:yesterday_download_traffic_rank" - // YesterdayUserTotalTrafficRank 昨日用户总流量排行榜 - YesterdayUserTotalTrafficRank = "node:yesterday_user_total_traffic_rank" - // YesterdayUserUploadTrafficRank 昨日用户上传流量排行榜 - YesterdayUserUploadTrafficRank = "node:yesterday_user_upload_traffic_rank" - // YesterdayUserDownloadTrafficRank 昨日用户下载流量排行榜 - YesterdayUserDownloadTrafficRank = "node:yesterday_user_download_traffic_rank" -) diff --git a/internal/model/cache/node.go b/internal/model/cache/node.go deleted file mode 100644 index 5e4d792..0000000 --- a/internal/model/cache/node.go +++ /dev/null @@ -1,584 +0,0 @@ -package cache - -import ( - "context" - "encoding/json" - "fmt" - "strconv" - "sync" - "time" - - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/redis/go-redis/v9" -) - -type NodeCacheClient struct { - *redis.Client - resetMutex sync.Mutex -} - -func NewNodeCacheClient(rds *redis.Client) *NodeCacheClient { - return &NodeCacheClient{ - Client: rds, - } -} - -// AddOnlineUserIP adds user's online IP -func (c *NodeCacheClient) AddOnlineUserIP(ctx context.Context, users []NodeOnlineUser) error { - if len(users) == 0 { - // No users to add - return nil - } - - // Use Pipeline to optimize Redis operations - pipe := c.Pipeline() - - // Add user online IPs and clean up expired IPs for each user - for _, user := range users { - if user.SID <= 0 || user.IP == "" { - logger.Errorf("invalid user data: uid=%d, ip=%s", user.SID, user.IP) - continue - } - - key := fmt.Sprintf(UserOnlineIpCacheKey, user.SID) - now := time.Now() - expireTime := now.Add(5 * time.Minute) - - // Clean up expired user online IPs - pipe.ZRemRangeByScore(ctx, key, "0", fmt.Sprintf("%d", now.Unix())) - pipe.ZRemRangeByScore(ctx, AllNodeOnlineUserCacheKey, "0", fmt.Sprintf("%d", now.Unix())) - - // Add or update user online IP - // XX: Only update elements that already exist - // NX: Only add new elements - _ = pipe.ZAdd(ctx, key, redis.Z{ - Score: float64(expireTime.Unix()), - Member: user.IP, - }).Err() - _ = pipe.ZAdd(ctx, AllNodeOnlineUserCacheKey, redis.Z{ - Score: float64(expireTime.Unix()), - Member: user.IP, - }).Err() - - // Set key expiration to 5 minutes (slightly longer than IP expiration) - pipe.Expire(ctx, key, 5*time.Minute) - pipe.Expire(ctx, AllNodeOnlineUserCacheKey, 5*time.Minute) - } - - // Execute all commands - _, err := pipe.Exec(ctx) - if err != nil { - return fmt.Errorf("failed to add node user online ip: %w", err) - } - return nil -} - -// GetUserOnlineIp gets user's online IPs -func (c *NodeCacheClient) GetUserOnlineIp(ctx context.Context, uid int64) ([]string, error) { - if uid <= 0 { - return nil, fmt.Errorf("invalid parameters: uid=%d", uid) - } - - // Get user's online IPs - ips, err := c.ZRevRangeByScore(ctx, fmt.Sprintf(UserOnlineIpCacheKey, uid), &redis.ZRangeBy{ - Min: "0", - Max: fmt.Sprintf("%d", time.Now().Add(5*time.Minute).Unix()), - Offset: 0, - Count: 100, - }).Result() - if err != nil { - return nil, fmt.Errorf("failed to get user online ip: %w", err) - } - return ips, nil -} - -// UpdateNodeOnlineUser updates node's online users and IPs -func (c *NodeCacheClient) UpdateNodeOnlineUser(ctx context.Context, nodeId int64, users []NodeOnlineUser) error { - if nodeId <= 0 || len(users) == 0 { - return fmt.Errorf("invalid parameters: nodeId=%d, users=%v", nodeId, users) - } - // Organize data - data := make(map[int64][]string) - for _, user := range users { - data[user.SID] = append(data[user.SID], user.IP) - } - - value, err := json.Marshal(data) - if err != nil { - return fmt.Errorf("failed to marshal data: %w", err) - } - - c.Set(ctx, fmt.Sprintf(NodeOnlineUserCacheKey, nodeId), value, time.Minute*5) - return nil -} - -// GetNodeOnlineUser gets node's online users and IPs -func (c *NodeCacheClient) GetNodeOnlineUser(ctx context.Context, nodeId int64) (map[int64][]string, error) { - if nodeId <= 0 { - return nil, fmt.Errorf("invalid parameters: nodeId=%d", nodeId) - } - value, err := c.Get(ctx, fmt.Sprintf(NodeOnlineUserCacheKey, nodeId)).Result() - if err != nil { - return nil, fmt.Errorf("failed to get node online user: %w", err) - } - var data map[int64][]string - if err := json.Unmarshal([]byte(value), &data); err != nil { - return nil, fmt.Errorf("failed to unmarshal data: %w", err) - } - return data, nil -} - -// AddUserTodayTraffic Add user's today traffic -func (c *NodeCacheClient) AddUserTodayTraffic(ctx context.Context, uid int64, upload, download int64) error { - if uid <= 0 || upload <= 0 { - return fmt.Errorf("invalid parameters: uid=%d, upload=%d", uid, upload) - } - pipe := c.Pipeline() - // User's today upload traffic - pipe.HIncrBy(ctx, UserTodayUploadTrafficCacheKey, fmt.Sprintf("%d", uid), upload) - // User's today download traffic - pipe.HIncrBy(ctx, UserTodayDownloadTrafficCacheKey, fmt.Sprintf("%d", uid), download) - // User's today total traffic - pipe.HIncrBy(ctx, UserTodayTotalTrafficCacheKey, fmt.Sprintf("%d", uid), upload+download) - // User's today traffic ranking - pipe.ZIncrBy(ctx, UserTodayUploadTrafficRankKey, float64(upload), fmt.Sprintf("%d", uid)) - pipe.ZIncrBy(ctx, UserTodayDownloadTrafficRankKey, float64(download), fmt.Sprintf("%d", uid)) - pipe.ZIncrBy(ctx, UserTodayTotalTrafficRankKey, float64(upload+download), fmt.Sprintf("%d", uid)) - - // All node upload traffic - pipe.IncrBy(ctx, AllNodeUploadTrafficCacheKey, upload) - // All node download traffic - pipe.IncrBy(ctx, AllNodeDownloadTrafficCacheKey, download) - // Execute commands - _, err := pipe.Exec(ctx) - if err != nil { - return fmt.Errorf("failed to add user today upload traffic: %w", err) - } - return nil -} - -// AddNodeTodayTraffic Add node's today traffic -func (c *NodeCacheClient) AddNodeTodayTraffic(ctx context.Context, nodeId int64, userTraffic []UserTraffic) error { - if nodeId <= 0 || len(userTraffic) == 0 { - return fmt.Errorf("invalid parameters: nodeId=%d, userTraffic=%v", nodeId, userTraffic) - } - pipe := c.Pipeline() - upload, download, total := c.calculateTraffic(userTraffic) - pipe.HIncrBy(ctx, NodeTodayUploadTrafficCacheKey, fmt.Sprintf("%d", nodeId), upload) - pipe.HIncrBy(ctx, NodeTodayDownloadTrafficCacheKey, fmt.Sprintf("%d", nodeId), download) - pipe.HIncrBy(ctx, NodeTodayTotalTrafficCacheKey, fmt.Sprintf("%d", nodeId), total) - pipe.ZIncrBy(ctx, NodeTodayUploadTrafficRankKey, float64(upload), fmt.Sprintf("%d", nodeId)) - pipe.ZIncrBy(ctx, NodeTodayDownloadTrafficRankKey, float64(download), fmt.Sprintf("%d", nodeId)) - pipe.ZIncrBy(ctx, NodeTodayTotalTrafficRankKey, float64(total), fmt.Sprintf("%d", nodeId)) - // Execute commands - _, err := pipe.Exec(ctx) - if err != nil { - return fmt.Errorf("failed to add node today upload traffic: %w", err) - } - return nil -} - -// Get user's traffic data -func (c *NodeCacheClient) getUserTrafficData(ctx context.Context, uid int64) (upload, download int64, err error) { - upload, err = c.HGet(ctx, UserTodayUploadTrafficCacheKey, fmt.Sprintf("%d", uid)).Int64() - if err != nil { - return 0, 0, fmt.Errorf("failed to get user today upload traffic: %w", err) - } - download, err = c.HGet(ctx, UserTodayDownloadTrafficCacheKey, fmt.Sprintf("%d", uid)).Int64() - if err != nil { - return 0, 0, fmt.Errorf("failed to get user today download traffic: %w", err) - } - return upload, download, nil -} - -// Get node's traffic data -func (c *NodeCacheClient) getNodeTrafficData(ctx context.Context, nodeId int64) (upload, download int64, err error) { - upload, err = c.HGet(ctx, NodeTodayUploadTrafficCacheKey, fmt.Sprintf("%d", nodeId)).Int64() - if err != nil { - return 0, 0, fmt.Errorf("failed to get node today upload traffic: %w", err) - } - download, err = c.HGet(ctx, NodeTodayDownloadTrafficCacheKey, fmt.Sprintf("%d", nodeId)).Int64() - if err != nil { - return 0, 0, fmt.Errorf("failed to get node today download traffic: %w", err) - } - return upload, download, nil -} - -// Parse ID -func (c *NodeCacheClient) parseID(member interface{}, idType string) (int64, error) { - id, err := strconv.ParseInt(member.(string), 10, 64) - if err != nil { - return 0, fmt.Errorf("failed to parse %s id %v: %w", idType, member, err) - } - return id, nil -} - -// GetUserTodayTotalTrafficRank Get user's today total traffic ranking top N -func (c *NodeCacheClient) GetUserTodayTotalTrafficRank(ctx context.Context, n int64) ([]UserTodayTrafficRank, error) { - if n <= 0 { - return nil, fmt.Errorf("invalid parameters: n=%d", n) - } - data, err := c.ZRevRangeWithScores(ctx, UserTodayTotalTrafficRankKey, 0, n-1).Result() - if err != nil { - return nil, fmt.Errorf("failed to get user today total traffic rank: %w", err) - } - users := make([]UserTodayTrafficRank, 0, len(data)) - for _, user := range data { - uid, err := c.parseID(user.Member, "user") - if err != nil { - logger.Errorf("%v", err) - continue - } - upload, download, err := c.getUserTrafficData(ctx, uid) - if err != nil { - logger.Errorf("%v", err) - continue - } - users = append(users, UserTodayTrafficRank{ - SID: uid, - Upload: upload, - Download: download, - Total: int64(user.Score), - }) - } - return users, nil -} - -// GetNodeTodayTotalTrafficRank Get node's today total traffic ranking top N -func (c *NodeCacheClient) GetNodeTodayTotalTrafficRank(ctx context.Context, n int64) ([]NodeTodayTrafficRank, error) { - if n <= 0 { - return nil, fmt.Errorf("invalid parameters: n=%d", n) - } - data, err := c.ZRevRangeWithScores(ctx, NodeTodayTotalTrafficRankKey, 0, n-1).Result() - if err != nil { - return nil, fmt.Errorf("failed to get node today total traffic rank: %w", err) - } - nodes := make([]NodeTodayTrafficRank, 0, len(data)) - for _, node := range data { - nodeId, err := c.parseID(node.Member, "node") - if err != nil { - logger.Errorf("%v", err) - continue - } - upload, download, err := c.getNodeTrafficData(ctx, nodeId) - if err != nil { - logger.Errorf("%v", err) - continue - } - nodes = append(nodes, NodeTodayTrafficRank{ - ID: nodeId, - Upload: upload, - Download: download, - Total: int64(node.Score), - }) - } - return nodes, nil -} - -// GetUserTodayUploadTrafficRank Get user's today upload traffic ranking top N -func (c *NodeCacheClient) GetUserTodayUploadTrafficRank(ctx context.Context, n int64) ([]UserTodayTrafficRank, error) { - if n <= 0 { - return nil, fmt.Errorf("invalid parameters: n=%d", n) - } - data, err := c.ZRevRangeWithScores(ctx, UserTodayUploadTrafficRankKey, 0, n-1).Result() - if err != nil { - return nil, fmt.Errorf("failed to get user today upload traffic rank: %w", err) - } - users := make([]UserTodayTrafficRank, 0, len(data)) - for _, user := range data { - uid, err := c.parseID(user.Member, "user") - if err != nil { - logger.Errorf("%v", err) - continue - } - upload, download, err := c.getUserTrafficData(ctx, uid) - if err != nil { - logger.Errorf("%v", err) - continue - } - users = append(users, UserTodayTrafficRank{ - SID: uid, - Upload: upload, - Download: download, - Total: int64(user.Score), - }) - } - return users, nil -} - -// GetUserTodayDownloadTrafficRank Get user's today download traffic ranking top N -func (c *NodeCacheClient) GetUserTodayDownloadTrafficRank(ctx context.Context, n int64) ([]UserTodayTrafficRank, error) { - if n <= 0 { - return nil, fmt.Errorf("invalid parameters: n=%d", n) - } - data, err := c.ZRevRangeWithScores(ctx, UserTodayDownloadTrafficRankKey, 0, n-1).Result() - if err != nil { - return nil, fmt.Errorf("failed to get user today download traffic rank: %w", err) - } - users := make([]UserTodayTrafficRank, 0, len(data)) - for _, user := range data { - uid, err := c.parseID(user.Member, "user") - if err != nil { - logger.Errorf("%v", err) - continue - } - upload, download, err := c.getUserTrafficData(ctx, uid) - if err != nil { - logger.Errorf("%v", err) - continue - } - users = append(users, UserTodayTrafficRank{ - SID: uid, - Upload: upload, - Download: download, - Total: int64(user.Score), - }) - } - return users, nil -} - -// GetNodeTodayUploadTrafficRank Get node's today upload traffic ranking top N -func (c *NodeCacheClient) GetNodeTodayUploadTrafficRank(ctx context.Context, n int64) ([]NodeTodayTrafficRank, error) { - if n <= 0 { - return nil, fmt.Errorf("invalid parameters: n=%d", n) - } - data, err := c.ZRevRangeWithScores(ctx, NodeTodayUploadTrafficRankKey, 0, n-1).Result() - if err != nil { - return nil, fmt.Errorf("failed to get node today upload traffic rank: %w", err) - } - nodes := make([]NodeTodayTrafficRank, 0, len(data)) - for _, node := range data { - nodeId, err := c.parseID(node.Member, "node") - if err != nil { - logger.Errorf("%v", err) - continue - } - upload, download, err := c.getNodeTrafficData(ctx, nodeId) - if err != nil { - logger.Errorf("%v", err) - continue - } - nodes = append(nodes, NodeTodayTrafficRank{ - ID: nodeId, - Upload: upload, - Download: download, - Total: int64(node.Score), - }) - } - return nodes, nil -} - -// GetNodeTodayDownloadTrafficRank Get node's today download traffic ranking top N -func (c *NodeCacheClient) GetNodeTodayDownloadTrafficRank(ctx context.Context, n int64) ([]NodeTodayTrafficRank, error) { - if n <= 0 { - return nil, fmt.Errorf("invalid parameters: n=%d", n) - } - data, err := c.ZRevRangeWithScores(ctx, NodeTodayDownloadTrafficRankKey, 0, n-1).Result() - if err != nil { - return nil, fmt.Errorf("failed to get node today download traffic rank: %w", err) - } - nodes := make([]NodeTodayTrafficRank, 0, len(data)) - for _, node := range data { - nodeId, err := c.parseID(node.Member, "node") - if err != nil { - logger.Errorf("%v", err) - continue - } - upload, download, err := c.getNodeTrafficData(ctx, nodeId) - if err != nil { - logger.Errorf("%v", err) - continue - } - nodes = append(nodes, NodeTodayTrafficRank{ - ID: nodeId, - Upload: upload, - Download: download, - Total: int64(node.Score), - }) - } - return nodes, nil -} - -// ResetTodayTrafficData Reset today's traffic data -func (c *NodeCacheClient) ResetTodayTrafficData(ctx context.Context) error { - c.resetMutex.Lock() - defer c.resetMutex.Unlock() - pipe := c.Pipeline() - pipe.Del(ctx, UserTodayUploadTrafficCacheKey) - pipe.Del(ctx, UserTodayDownloadTrafficCacheKey) - pipe.Del(ctx, UserTodayTotalTrafficCacheKey) - pipe.Del(ctx, NodeTodayUploadTrafficCacheKey) - pipe.Del(ctx, NodeTodayDownloadTrafficCacheKey) - pipe.Del(ctx, NodeTodayTotalTrafficCacheKey) - pipe.Del(ctx, UserTodayUploadTrafficRankKey) - pipe.Del(ctx, UserTodayDownloadTrafficRankKey) - pipe.Del(ctx, UserTodayTotalTrafficRankKey) - pipe.Del(ctx, NodeTodayUploadTrafficRankKey) - pipe.Del(ctx, NodeTodayDownloadTrafficRankKey) - pipe.Del(ctx, NodeTodayTotalTrafficRankKey) - pipe.Del(ctx, AllNodeDownloadTrafficCacheKey) - pipe.Del(ctx, AllNodeUploadTrafficCacheKey) - _, err := pipe.Exec(ctx) - if err != nil { - return fmt.Errorf("failed to reset today traffic data: %w", err) - } - return nil -} - -// Calculate traffic -func (c *NodeCacheClient) calculateTraffic(data []UserTraffic) (upload, download, total int64) { - for _, userTraffic := range data { - upload += userTraffic.Upload - download += userTraffic.Download - total += userTraffic.Upload + userTraffic.Download - } - return upload, download, total -} - -// GetAllNodeOnlineUser Get all node online user -func (c *NodeCacheClient) GetAllNodeOnlineUser(ctx context.Context) ([]string, error) { - users, err := c.ZRevRange(ctx, AllNodeOnlineUserCacheKey, 0, -1).Result() - if err != nil { - return nil, fmt.Errorf("failed to get all node online user: %w", err) - } - return users, nil -} - -// UpdateNodeStatus Update node status -func (c *NodeCacheClient) UpdateNodeStatus(ctx context.Context, nodeId int64, status NodeStatus) error { - // 参数验证 - if nodeId <= 0 { - return fmt.Errorf("invalid node id: %d", nodeId) - } - - // 验证状态数据 - if status.UpdatedAt <= 0 { - return fmt.Errorf("invalid status data: updated_at=%d", status.UpdatedAt) - } - - // 序列化状态数据 - value, err := json.Marshal(status) - if err != nil { - return fmt.Errorf("failed to marshal node status: %w", err) - } - - // 使用 Pipeline 优化性能 - pipe := c.Pipeline() - - // 设置状态数据 - pipe.Set(ctx, fmt.Sprintf(NodeStatusCacheKey, nodeId), value, time.Minute*5) - - // 执行命令 - _, err = pipe.Exec(ctx) - if err != nil { - return fmt.Errorf("failed to update node status: %w", err) - } - - return nil -} - -// GetNodeStatus Get node status -func (c *NodeCacheClient) GetNodeStatus(ctx context.Context, nodeId int64) (NodeStatus, error) { - status, err := c.Get(ctx, fmt.Sprintf(NodeStatusCacheKey, nodeId)).Result() - if err != nil { - return NodeStatus{}, fmt.Errorf("failed to get node status: %w", err) - } - var nodeStatus NodeStatus - if err := json.Unmarshal([]byte(status), &nodeStatus); err != nil { - return NodeStatus{}, fmt.Errorf("failed to unmarshal node status: %w", err) - } - return nodeStatus, nil -} - -// GetOnlineNodeStatusCount Get Online Node Status Count -func (c *NodeCacheClient) GetOnlineNodeStatusCount(ctx context.Context) (int64, error) { - // 获取所有节点Key - keys, err := c.Keys(ctx, "node:status:*").Result() - if err != nil { - return 0, fmt.Errorf("failed to get all node status keys: %w", err) - } - var count int64 - for _, key := range keys { - status, err := c.Get(ctx, key).Result() - if err != nil { - logger.Errorf("failed to get node status: %v", err.Error()) - continue - } - if status != "" { - count++ - } - } - return count, nil -} - -// GetAllNodeUploadTraffic Get all node upload traffic -func (c *NodeCacheClient) GetAllNodeUploadTraffic(ctx context.Context) (int64, error) { - upload, err := c.Get(ctx, AllNodeUploadTrafficCacheKey).Int64() - if err != nil { - return 0, fmt.Errorf("failed to get all node upload traffic: %w", err) - } - return upload, nil -} - -// GetAllNodeDownloadTraffic Get all node download traffic -func (c *NodeCacheClient) GetAllNodeDownloadTraffic(ctx context.Context) (int64, error) { - download, err := c.Get(ctx, AllNodeDownloadTrafficCacheKey).Int64() - if err != nil { - return 0, fmt.Errorf("failed to get all node download traffic: %w", err) - } - return download, nil -} - -// UpdateYesterdayNodeTotalTrafficRank Update yesterday node total traffic rank -func (c *NodeCacheClient) UpdateYesterdayNodeTotalTrafficRank(ctx context.Context, nodes []NodeTodayTrafficRank) error { - expireAt := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Local).Add(time.Hour * 24) - t := time.Until(expireAt) - pipe := c.Pipeline() - value, _ := json.Marshal(nodes) - pipe.Set(ctx, YesterdayNodeTotalTrafficRank, value, t) - _, err := pipe.Exec(ctx) - if err != nil { - return fmt.Errorf("failed to update yesterday node total traffic rank: %w", err) - } - return nil -} - -// UpdateYesterdayUserTotalTrafficRank Update yesterday user total traffic rank -func (c *NodeCacheClient) UpdateYesterdayUserTotalTrafficRank(ctx context.Context, users []UserTodayTrafficRank) error { - expireAt := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Local).Add(time.Hour * 24) - t := time.Until(expireAt) - pipe := c.Pipeline() - value, _ := json.Marshal(users) - pipe.Set(ctx, YesterdayUserTotalTrafficRank, value, t) - _, err := pipe.Exec(ctx) - if err != nil { - return fmt.Errorf("failed to update yesterday user total traffic rank: %w", err) - } - return nil -} - -// GetYesterdayNodeTotalTrafficRank Get yesterday node total traffic rank -func (c *NodeCacheClient) GetYesterdayNodeTotalTrafficRank(ctx context.Context) ([]NodeTodayTrafficRank, error) { - value, err := c.Get(ctx, YesterdayNodeTotalTrafficRank).Result() - if err != nil { - return nil, fmt.Errorf("failed to get yesterday node total traffic rank: %w", err) - } - var nodes []NodeTodayTrafficRank - if err := json.Unmarshal([]byte(value), &nodes); err != nil { - return nil, fmt.Errorf("failed to unmarshal yesterday node total traffic rank: %w", err) - } - return nodes, nil -} - -// GetYesterdayUserTotalTrafficRank Get yesterday user total traffic rank -func (c *NodeCacheClient) GetYesterdayUserTotalTrafficRank(ctx context.Context) ([]UserTodayTrafficRank, error) { - value, err := c.Get(ctx, YesterdayUserTotalTrafficRank).Result() - if err != nil { - return nil, fmt.Errorf("failed to get yesterday user total traffic rank: %w", err) - } - var users []UserTodayTrafficRank - if err := json.Unmarshal([]byte(value), &users); err != nil { - return nil, fmt.Errorf("failed to unmarshal yesterday user total traffic rank: %w", err) - } - return users, nil -} diff --git a/internal/model/cache/node_test.go b/internal/model/cache/node_test.go deleted file mode 100644 index b7660ab..0000000 --- a/internal/model/cache/node_test.go +++ /dev/null @@ -1,575 +0,0 @@ -package cache - -import ( - "context" - "encoding/json" - "fmt" - "testing" - "time" - - "github.com/alicebob/miniredis/v2" - "github.com/redis/go-redis/v9" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// Create a test Redis client -func newTestRedisClient(t *testing.T) *redis.Client { - mr, err := miniredis.Run() - require.NoError(t, err) - - client := redis.NewClient(&redis.Options{ - Addr: mr.Addr(), - }) - require.NoError(t, client.Ping(context.Background()).Err()) - return client -} - -// Clean up test data -func cleanupTestData(t *testing.T, client *redis.Client) { - ctx := context.Background() - keys := []string{ - UserTodayUploadTrafficCacheKey, - UserTodayDownloadTrafficCacheKey, - UserTodayTotalTrafficCacheKey, - NodeTodayUploadTrafficCacheKey, - NodeTodayDownloadTrafficCacheKey, - NodeTodayTotalTrafficCacheKey, - UserTodayUploadTrafficRankKey, - UserTodayDownloadTrafficRankKey, - UserTodayTotalTrafficRankKey, - NodeTodayUploadTrafficRankKey, - NodeTodayDownloadTrafficRankKey, - NodeTodayTotalTrafficRankKey, - } - - // Clean up all cache keys - for _, key := range keys { - require.NoError(t, client.Del(ctx, key).Err()) - } - - // Clean up user online IP cache - for uid := int64(1); uid <= 3; uid++ { - require.NoError(t, client.Del(ctx, fmt.Sprintf(UserOnlineIpCacheKey, uid)).Err()) - } - - // Clean up node online user cache - for nodeId := int64(1); nodeId <= 3; nodeId++ { - require.NoError(t, client.Del(ctx, fmt.Sprintf(NodeOnlineUserCacheKey, nodeId)).Err()) - } -} - -func TestNodeCacheClient_AddUserTodayTraffic(t *testing.T) { - client := newTestRedisClient(t) - defer cleanupTestData(t, client) - - cache := NewNodeCacheClient(client) - ctx := context.Background() - - tests := []struct { - name string - uid int64 - upload int64 - download int64 - wantErr bool - }{ - { - name: "Add traffic normally", - uid: 1, - upload: 100, - download: 200, - wantErr: false, - }, - { - name: "Invalid SID", - uid: 0, - upload: 100, - download: 200, - wantErr: true, - }, - { - name: "Invalid upload traffic", - uid: 1, - upload: 0, - download: 200, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := cache.AddUserTodayTraffic(ctx, tt.uid, tt.upload, tt.download) - if tt.wantErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - - // Verify data is added correctly - upload, err := client.HGet(ctx, UserTodayUploadTrafficCacheKey, "1").Int64() - assert.NoError(t, err) - assert.Equal(t, tt.upload, upload) - - download, err := client.HGet(ctx, UserTodayDownloadTrafficCacheKey, "1").Int64() - assert.NoError(t, err) - assert.Equal(t, tt.download, download) - }) - } -} - -func TestNodeCacheClient_AddNodeTodayTraffic(t *testing.T) { - client := newTestRedisClient(t) - defer cleanupTestData(t, client) - - cache := NewNodeCacheClient(client) - ctx := context.Background() - - tests := []struct { - name string - nodeId int64 - userTraffic []UserTraffic - wantErr bool - }{ - { - name: "Add node traffic normally", - nodeId: 1, - userTraffic: []UserTraffic{ - {UID: 1, Upload: 100, Download: 200}, - {UID: 2, Upload: 300, Download: 400}, - }, - wantErr: false, - }, - { - name: "Invalid node ID", - nodeId: 0, - userTraffic: []UserTraffic{ - {UID: 1, Upload: 100, Download: 200}, - }, - wantErr: true, - }, - { - name: "Empty user traffic data", - nodeId: 1, - userTraffic: []UserTraffic{}, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := cache.AddNodeTodayTraffic(ctx, tt.nodeId, tt.userTraffic) - if tt.wantErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - - // Verify data is added correctly - upload, err := client.HGet(ctx, NodeTodayUploadTrafficCacheKey, "1").Int64() - assert.NoError(t, err) - assert.Equal(t, int64(400), upload) // 100 + 300 - - download, err := client.HGet(ctx, NodeTodayDownloadTrafficCacheKey, "1").Int64() - assert.NoError(t, err) - assert.Equal(t, int64(600), download) // 200 + 400 - }) - } -} - -func TestNodeCacheClient_GetUserTodayTrafficRank(t *testing.T) { - client := newTestRedisClient(t) - defer cleanupTestData(t, client) - - cache := NewNodeCacheClient(client) - ctx := context.Background() - - // Prepare test data - testData := []struct { - uid int64 - upload int64 - download int64 - }{ - {1, 100, 200}, - {2, 300, 400}, - {3, 500, 600}, - } - - for _, data := range testData { - err := cache.AddUserTodayTraffic(ctx, data.uid, data.upload, data.download) - require.NoError(t, err) - } - - tests := []struct { - name string - n int64 - wantErr bool - }{ - { - name: "Get top 2 ranks", - n: 2, - wantErr: false, - }, - { - name: "Get all ranks", - n: 3, - wantErr: false, - }, - { - name: "Invalid N value", - n: 0, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ranks, err := cache.GetUserTodayTotalTrafficRank(ctx, tt.n) - if tt.wantErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - assert.Len(t, ranks, int(tt.n)) - - // Verify sorting is correct - for i := 1; i < len(ranks); i++ { - assert.GreaterOrEqual(t, ranks[i-1].Total, ranks[i].Total) - } - }) - } -} - -func TestNodeCacheClient_ResetTodayTrafficData(t *testing.T) { - client := newTestRedisClient(t) - defer cleanupTestData(t, client) - - cache := NewNodeCacheClient(client) - ctx := context.Background() - - // Prepare test data - err := cache.AddUserTodayTraffic(ctx, 1, 100, 200) - require.NoError(t, err) - err = cache.AddNodeTodayTraffic(ctx, 1, []UserTraffic{{UID: 1, Upload: 100, Download: 200}}) - require.NoError(t, err) - - // Test reset functionality - err = cache.ResetTodayTrafficData(ctx) - assert.NoError(t, err) - - // Verify data is cleared - keys := []string{ - UserTodayUploadTrafficCacheKey, - UserTodayDownloadTrafficCacheKey, - UserTodayTotalTrafficCacheKey, - NodeTodayUploadTrafficCacheKey, - NodeTodayDownloadTrafficCacheKey, - NodeTodayTotalTrafficCacheKey, - } - - for _, key := range keys { - exists, err := client.Exists(ctx, key).Result() - assert.NoError(t, err) - assert.Equal(t, int64(0), exists) - } -} - -func TestNodeCacheClient_GetNodeTodayTrafficRank(t *testing.T) { - client := newTestRedisClient(t) - defer cleanupTestData(t, client) - - cache := NewNodeCacheClient(client) - ctx := context.Background() - - // Prepare test data - testData := []struct { - nodeId int64 - traffic []UserTraffic - }{ - {1, []UserTraffic{{UID: 1, Upload: 100, Download: 200}}}, - {2, []UserTraffic{{UID: 2, Upload: 300, Download: 400}}}, - {3, []UserTraffic{{UID: 3, Upload: 500, Download: 600}}}, - } - - for _, data := range testData { - err := cache.AddNodeTodayTraffic(ctx, data.nodeId, data.traffic) - require.NoError(t, err) - } - - tests := []struct { - name string - n int64 - wantErr bool - }{ - { - name: "Get top 2 ranks", - n: 2, - wantErr: false, - }, - { - name: "Get all ranks", - n: 3, - wantErr: false, - }, - { - name: "Invalid N value", - n: 0, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ranks, err := cache.GetNodeTodayTotalTrafficRank(ctx, tt.n) - if tt.wantErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - assert.Len(t, ranks, int(tt.n)) - - // Verify sorting is correct - for i := 1; i < len(ranks); i++ { - assert.GreaterOrEqual(t, ranks[i-1].Total, ranks[i].Total) - } - }) - } -} - -func TestNodeCacheClient_AddNodeOnlineUser(t *testing.T) { - client := newTestRedisClient(t) - defer cleanupTestData(t, client) - - cache := NewNodeCacheClient(client) - ctx := context.Background() - - tests := []struct { - name string - nodeId int64 - users []NodeOnlineUser - wantErr bool - }{ - { - name: "Add online users normally", - nodeId: 1, - users: []NodeOnlineUser{ - {SID: 1, IP: "192.168.1.1"}, - {SID: 2, IP: "192.168.1.2"}, - }, - wantErr: false, - }, - { - name: "Invalid node ID", - nodeId: 0, - users: []NodeOnlineUser{ - {SID: 1, IP: "192.168.1.1"}, - }, - wantErr: false, - }, - { - name: "Empty user list", - nodeId: 1, - users: []NodeOnlineUser{}, - wantErr: false, - }, - { - name: "Add duplicate user IP", - nodeId: 1, - users: []NodeOnlineUser{ - {SID: 1, IP: "192.168.1.1"}, - {SID: 1, IP: "192.168.1.1"}, - }, - wantErr: false, - }, - { - name: "Multiple IPs for same user", - nodeId: 1, - users: []NodeOnlineUser{ - {SID: 1, IP: "192.168.1.1"}, - {SID: 1, IP: "192.168.1.2"}, - }, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := cache.AddOnlineUserIP(ctx, tt.users) - if tt.wantErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - - // Verify data is added correctly - for _, user := range tt.users { - // Get user online IPs - ips, err := cache.GetUserOnlineIp(ctx, user.SID) - assert.NoError(t, err) - assert.Contains(t, ips, user.IP) - - // Verify score is within valid range (current time to 5 minutes later) - score, err := client.ZScore(ctx, fmt.Sprintf(UserOnlineIpCacheKey, user.SID), user.IP).Result() - assert.NoError(t, err) - now := time.Now().Unix() - assert.GreaterOrEqual(t, score, float64(now)) - assert.LessOrEqual(t, score, float64(now+300)) // 5 minutes = 300 seconds - - // Verify key exists - exists, err := client.Exists(ctx, fmt.Sprintf(UserOnlineIpCacheKey, user.SID)).Result() - assert.NoError(t, err) - assert.Equal(t, int64(1), exists) - } - }) - } -} - -func TestNodeCacheClient_GetUserOnlineIp(t *testing.T) { - client := newTestRedisClient(t) - defer cleanupTestData(t, client) - - cache := NewNodeCacheClient(client) - ctx := context.Background() - - // Prepare test data - testData := []struct { - nodeId int64 - users []NodeOnlineUser - }{ - { - nodeId: 1, - users: []NodeOnlineUser{ - {SID: 1, IP: "192.168.1.1"}, - {SID: 1, IP: "192.168.1.2"}, - {SID: 2, IP: "192.168.1.3"}, - }, - }, - } - - // Add test data - for _, data := range testData { - err := cache.AddOnlineUserIP(ctx, data.users) - require.NoError(t, err) - } - - tests := []struct { - name string - uid int64 - wantErr bool - wantIPs []string - }{ - { - name: "Get existing user IPs", - uid: 1, - wantErr: false, - wantIPs: []string{"192.168.1.1", "192.168.1.2"}, - }, - { - name: "Get another user's IPs", - uid: 2, - wantErr: false, - wantIPs: []string{"192.168.1.3"}, - }, - { - name: "Get non-existent user IPs", - uid: 3, - wantErr: false, - wantIPs: []string{}, - }, - { - name: "Invalid user ID", - uid: 0, - wantErr: true, - }, - { - name: "Expired IPs should not be returned", - uid: 1, - wantErr: false, - wantIPs: []string{"192.168.1.1", "192.168.1.2"}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ips, err := cache.GetUserOnlineIp(ctx, tt.uid) - if tt.wantErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - assert.ElementsMatch(t, tt.wantIPs, ips) - - // Verify all returned IPs are valid - for _, ip := range ips { - score, err := client.ZScore(ctx, fmt.Sprintf(UserOnlineIpCacheKey, tt.uid), ip).Result() - assert.NoError(t, err) - now := time.Now().Unix() - assert.GreaterOrEqual(t, score, float64(now)) - } - }) - } -} - -func TestNodeCacheClient_UpdateNodeOnlineUser(t *testing.T) { - client := newTestRedisClient(t) - defer cleanupTestData(t, client) - - cache := NewNodeCacheClient(client) - ctx := context.Background() - - tests := []struct { - name string - nodeId int64 - users []NodeOnlineUser - wantErr bool - }{ - { - name: "Update online users normally", - nodeId: 1, - users: []NodeOnlineUser{ - {SID: 1, IP: "192.168.1.1"}, - {SID: 2, IP: "192.168.1.2"}, - }, - wantErr: false, - }, - { - name: "Invalid node ID", - nodeId: 0, - users: []NodeOnlineUser{ - {SID: 1, IP: "192.168.1.1"}, - }, - wantErr: true, - }, - { - name: "Empty user list", - nodeId: 1, - users: []NodeOnlineUser{}, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := cache.UpdateNodeOnlineUser(ctx, tt.nodeId, tt.users) - if tt.wantErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - - // Verify data is updated correctly - data, err := client.Get(ctx, fmt.Sprintf(NodeOnlineUserCacheKey, tt.nodeId)).Result() - assert.NoError(t, err) - - var result map[int64][]string - err = json.Unmarshal([]byte(data), &result) - assert.NoError(t, err) - - // Verify data content - for _, user := range tt.users { - ips, exists := result[user.SID] - assert.True(t, exists) - assert.Contains(t, ips, user.IP) - } - }) - } -} diff --git a/internal/model/cache/types.go b/internal/model/cache/types.go deleted file mode 100644 index 89b6144..0000000 --- a/internal/model/cache/types.go +++ /dev/null @@ -1,34 +0,0 @@ -package cache - -type NodeOnlineUser struct { - SID int64 - IP string -} - -type NodeTodayTrafficRank struct { - ID int64 - Name string - Upload int64 - Download int64 - Total int64 -} - -type UserTodayTrafficRank struct { - SID int64 - Upload int64 - Download int64 - Total int64 -} - -type UserTraffic struct { - UID int64 - Upload int64 - Download int64 -} - -type NodeStatus struct { - Cpu float64 - Mem float64 - Disk float64 - UpdatedAt int64 -} diff --git a/internal/model/client/application.go b/internal/model/client/application.go new file mode 100644 index 0000000..ff121d0 --- /dev/null +++ b/internal/model/client/application.go @@ -0,0 +1,75 @@ +package client + +import ( + "encoding/json" + "time" +) + +type SubscribeApplication struct { + Id int64 `gorm:"primaryKey"` + Name string `gorm:"type:varchar(255);default:'';not null;comment:Application Name"` + Icon string `gorm:"type:MEDIUMTEXT;default:null;comment:Application Icon"` + Description string `gorm:"type:varchar(255);default:null;comment:Application Description"` + Scheme string `gorm:"type:varchar(255);default:'';not null;comment:Scheme"` + UserAgent string `gorm:"type:varchar(255);default:'';not null;comment:User Agent"` + IsDefault bool `gorm:"type:tinyint(1);not null;default:0;comment:Is Default Application"` + SubscribeTemplate string `gorm:"type:MEDIUMTEXT;default:null;comment:Subscribe Template"` + OutputFormat string `gorm:"type:varchar(50);default:'yaml';not null;comment:Output Format"` + DownloadLink string `gorm:"type:text;not null;comment:Download Link"` + CreatedAt time.Time `gorm:"<-:create;comment:Create Time"` + UpdatedAt time.Time `gorm:"comment:Update Time"` +} + +func (SubscribeApplication) TableName() string { + return "subscribe_application" +} + +type DownloadLink struct { + IOS string `json:"ios,omitempty"` + Android string `json:"android,omitempty"` + Windows string `json:"windows,omitempty"` + Mac string `json:"mac,omitempty"` + Linux string `json:"linux,omitempty"` + Harmony string `json:"harmony,omitempty"` +} + +// GetDownloadLink returns the download link for the specified platform. +func (d *DownloadLink) GetDownloadLink(platform string) string { + if d == nil { + return "" + } + switch platform { + case "ios": + return d.IOS + case "android": + return d.Android + case "windows": + return d.Windows + case "mac": + return d.Mac + case "linux": + return d.Linux + case "harmony": + return d.Harmony + default: + return "" + } +} + +// Marshal serializes the DownloadLink to JSON format. +func (d *DownloadLink) Marshal() ([]byte, error) { + if d == nil { + var empty DownloadLink + return json.Marshal(empty) + } + return json.Marshal(d) +} + +// Unmarshal parses the JSON-encoded data and stores the result in the DownloadLink. +func (d *DownloadLink) Unmarshal(data []byte) error { + if data == nil || len(data) == 0 { + *d = DownloadLink{} + return nil + } + return json.Unmarshal(data, d) +} diff --git a/internal/model/client/default.go b/internal/model/client/default.go new file mode 100644 index 0000000..c52108f --- /dev/null +++ b/internal/model/client/default.go @@ -0,0 +1,81 @@ +package client + +import ( + "context" + + "gorm.io/gorm" +) + +type ( + Model interface { + subscribeApplicationModel + } + subscribeApplicationModel interface { + Insert(ctx context.Context, data *SubscribeApplication) error + FindOne(ctx context.Context, id int64) (*SubscribeApplication, error) + Update(ctx context.Context, data *SubscribeApplication) error + Delete(ctx context.Context, id int64) error + List(ctx context.Context) ([]*SubscribeApplication, error) + Transaction(ctx context.Context, fn func(db *gorm.DB) error) error + } + DefaultSubscribeApplicationModel struct { + *gorm.DB + } +) + +func NewSubscribeApplicationModel(db *gorm.DB) Model { + return &DefaultSubscribeApplicationModel{ + DB: db, + } +} + +func (m *DefaultSubscribeApplicationModel) Insert(ctx context.Context, data *SubscribeApplication) error { + if err := m.WithContext(ctx).Model(&SubscribeApplication{}).Create(data).Error; err != nil { + return err + } + return nil +} + +func (m *DefaultSubscribeApplicationModel) FindOne(ctx context.Context, id int64) (*SubscribeApplication, error) { + var resp SubscribeApplication + if err := m.WithContext(ctx).Model(&SubscribeApplication{}).Where("id = ?", id).First(&resp).Error; err != nil { + return nil, err + } + return &resp, nil +} + +func (m *DefaultSubscribeApplicationModel) Update(ctx context.Context, data *SubscribeApplication) error { + if _, err := m.FindOne(ctx, data.Id); err != nil { + return err + } + if err := m.WithContext(ctx).Model(&SubscribeApplication{}).Where("`id` = ?", data.Id).Save(data).Error; err != nil { + return err + } + return nil +} + +func (m *DefaultSubscribeApplicationModel) Delete(ctx context.Context, id int64) error { + if err := m.WithContext(ctx).Model(&SubscribeApplication{}).Where("`id` = ?", id).Delete(&SubscribeApplication{}).Error; err != nil { + return err + } + return nil +} + +func (m *DefaultSubscribeApplicationModel) Transaction(ctx context.Context, fn func(db *gorm.DB) error) error { + tx := m.WithContext(ctx).Begin() + if err := fn(tx); err != nil { + if rbErr := tx.Rollback().Error; rbErr != nil { + return rbErr + } + return err + } + return tx.Commit().Error +} + +func (m *DefaultSubscribeApplicationModel) List(ctx context.Context) ([]*SubscribeApplication, error) { + var resp []*SubscribeApplication + if err := m.WithContext(ctx).Find(&resp).Error; err != nil { + return nil, err + } + return resp, nil +} diff --git a/internal/model/coupon/default.go b/internal/model/coupon/default.go index 2abca66..17df8f9 100644 --- a/internal/model/coupon/default.go +++ b/internal/model/coupon/default.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/perfect-panel/ppanel-server/pkg/cache" + "github.com/perfect-panel/server/pkg/cache" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) diff --git a/internal/model/document/default.go b/internal/model/document/default.go index 921fdcb..908747a 100644 --- a/internal/model/document/default.go +++ b/internal/model/document/default.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/perfect-panel/ppanel-server/pkg/cache" + "github.com/perfect-panel/server/pkg/cache" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) diff --git a/internal/model/log/default.go b/internal/model/log/default.go index 5b8d731..242891a 100644 --- a/internal/model/log/default.go +++ b/internal/model/log/default.go @@ -6,74 +6,50 @@ import ( "gorm.io/gorm" ) -var _ Model = (*customLogModel)(nil) +var _ Model = (*customSystemLogModel)(nil) type ( Model interface { - messageLogModel + systemLogModel + customSystemLogLogicModel } - messageLogModel interface { - InsertMessageLog(ctx context.Context, data *MessageLog) error - FindOneMessageLog(ctx context.Context, id int64) (*MessageLog, error) - UpdateMessageLog(ctx context.Context, data *MessageLog) error - DeleteMessageLog(ctx context.Context, id int64) error - FindMessageLogList(ctx context.Context, page, size int, filter MessageLogFilterParams) (int64, []*MessageLog, error) + systemLogModel interface { + Insert(ctx context.Context, data *SystemLog) error + FindOne(ctx context.Context, id int64) (*SystemLog, error) + Update(ctx context.Context, data *SystemLog) error + Delete(ctx context.Context, id int64) error } - - customLogModel struct { + customSystemLogModel struct { *defaultLogModel } defaultLogModel struct { - Connection *gorm.DB + *gorm.DB } ) -func newLogModel(db *gorm.DB) *defaultLogModel { +func newSystemLogModel(db *gorm.DB) *defaultLogModel { return &defaultLogModel{ - Connection: db, + DB: db, } } -func (m *defaultLogModel) InsertMessageLog(ctx context.Context, data *MessageLog) error { - return m.Connection.WithContext(ctx).Create(&data).Error +func (m *defaultLogModel) Insert(ctx context.Context, data *SystemLog) error { + return m.WithContext(ctx).Create(data).Error } -func (m *defaultLogModel) FindOneMessageLog(ctx context.Context, id int64) (*MessageLog, error) { - var resp MessageLog - err := m.Connection.WithContext(ctx).Model(&MessageLog{}).Where("`id` = ?", id).First(&resp).Error - return &resp, err +func (m *defaultLogModel) FindOne(ctx context.Context, id int64) (*SystemLog, error) { + var log SystemLog + err := m.WithContext(ctx).Where("id = ?", id).First(&log).Error + if err != nil { + return nil, err + } + return &log, nil } -func (m *defaultLogModel) UpdateMessageLog(ctx context.Context, data *MessageLog) error { - return m.Connection.WithContext(ctx).Model(&MessageLog{}).Where("id = ?", data.Id).Updates(data).Error +func (m *defaultLogModel) Update(ctx context.Context, data *SystemLog) error { + return m.WithContext(ctx).Where("`id` = ?", data.Id).Save(data).Error } -func (m *defaultLogModel) DeleteMessageLog(ctx context.Context, id int64) error { - return m.Connection.WithContext(ctx).Model(&MessageLog{}).Where("id = ?", id).Delete(&MessageLog{}).Error -} - -func (m *defaultLogModel) FindMessageLogList(ctx context.Context, page, size int, filter MessageLogFilterParams) (int64, []*MessageLog, error) { - var list []*MessageLog - var total int64 - conn := m.Connection.WithContext(ctx).Model(&MessageLog{}) - if filter.Type != "" { - conn = conn.Where("`type` = ?", filter.Type) - } - if filter.Platform != "" { - conn = conn.Where("`platform` = ?", filter.Platform) - } - if filter.To != "" { - conn = conn.Where("`to` LIKE ?", "%"+filter.To+"%") - } - if filter.Subject != "" { - conn = conn.Where("`subject` LIKE ?", "%"+filter.Subject+"%") - } - if filter.Content != "" { - conn = conn.Where("`content` = ?", "%"+filter.Content+"%") - } - if filter.Status > 0 { - conn = conn.Where("`status` = ?", filter.Status) - } - err := conn.Count(&total).Offset((page - 1) * size).Limit(size).Find(&list).Error - return total, list, err +func (m *defaultLogModel) Delete(ctx context.Context, id int64) error { + return m.WithContext(ctx).Where("`id` = ?", id).Delete(&SystemLog{}).Error } diff --git a/internal/model/log/log.go b/internal/model/log/log.go index af66746..af3eb98 100644 --- a/internal/model/log/log.go +++ b/internal/model/log/log.go @@ -1,45 +1,424 @@ package log -import "time" - -type MessageType int - -const ( - Email MessageType = iota + 1 - Mobile +import ( + "encoding/json" + "time" ) -func (t MessageType) String() string { - switch t { - case Email: - return "email" - case Mobile: - return "mobile" - } - return "unknown" +type Type uint8 + +/* + +Log Types: + 1X Message Logs + 2X Subscription Logs + 3X User Logs + 4X Traffic Ranking Logs +*/ + +const ( + TypeEmailMessage Type = 10 // Message log + TypeMobileMessage Type = 11 // Mobile message log + TypeSubscribe Type = 20 // Subscription log + TypeSubscribeTraffic Type = 21 // Subscription traffic log + TypeServerTraffic Type = 22 // Server traffic log + TypeResetSubscribe Type = 23 // Reset subscription log + TypeLogin Type = 30 // Login log + TypeRegister Type = 31 // Registration log + TypeBalance Type = 32 // Balance log + TypeCommission Type = 33 // Commission log + TypeGift Type = 34 // Gift log + TypeUserTrafficRank Type = 40 // Top 10 User traffic rank log + TypeServerTrafficRank Type = 41 // Top 10 Server traffic rank log + TypeTrafficStat Type = 42 // Daily traffic statistics log +) +const ( + ResetSubscribeTypeAuto uint16 = 231 // Auto reset + ResetSubscribeTypeAdvance uint16 = 232 // Advance reset + ResetSubscribeTypePaid uint16 = 233 // Paid reset + ResetSubscribeTypeQuota uint16 = 234 // Quota reset + BalanceTypeRecharge uint16 = 321 // Recharge + BalanceTypeWithdraw uint16 = 322 // Withdraw + BalanceTypePayment uint16 = 323 // Payment + BalanceTypeRefund uint16 = 324 // Refund + BalanceTypeAdjust uint16 = 326 // Admin Adjust + BalanceTypeReward uint16 = 325 // Reward + CommissionTypePurchase uint16 = 331 // Purchase + CommissionTypeRenewal uint16 = 332 // Renewal + CommissionTypeRefund uint16 = 333 // Refund + commissionTypeWithdraw uint16 = 334 // withdraw + CommissionTypeAdjust uint16 = 335 // Admin Adjust + GiftTypeIncrease uint16 = 341 // Increase + GiftTypeReduce uint16 = 342 // Reduce +) + +// Uint8 converts Type to uint8. +func (t Type) Uint8() uint8 { + return uint8(t) } -type MessageLog struct { - Id int64 `gorm:"primaryKey"` - Type string `gorm:"type:varchar(50);not null;default:'email';comment:Message Type"` - Platform string `gorm:"type:varchar(50);not null;default:'smtp';comment:Platform"` - To string `gorm:"type:text;not null;comment:To"` - Subject string `gorm:"type:varchar(255);not null;default:'';comment:Subject"` - Content string `gorm:"type:text;comment:Content"` - Status int `gorm:"type:tinyint(1);not null;default:0;comment:Status"` +// SystemLog represents a log entry in the system. +type SystemLog struct { + Id int64 `gorm:"primaryKey;AUTO_INCREMENT"` + Type uint8 `gorm:"index:idx_type;type:tinyint(1);not null;default:0;comment:Log Type: 1: Email Message 2: Mobile Message 3: Subscribe 4: Subscribe Traffic 5: Server Traffic 6: Login 7: Register 8: Balance 9: Commission 10: Reset Subscribe 11: Gift"` + Date string `gorm:"type:varchar(20);default:null;comment:Log Date"` + ObjectID int64 `gorm:"index:idx_object_id;type:bigint(20);not null;default:0;comment:Object ID"` + Content string `gorm:"type:text;not null;comment:Log Content"` CreatedAt time.Time `gorm:"<-:create;comment:Create Time"` - UpdatedAt time.Time `gorm:"comment:Update Time"` } -func (m *MessageLog) TableName() string { - return "message_log" +// TableName returns the name of the table for SystemLogs. +func (SystemLog) TableName() string { + return "system_logs" } -type MessageLogFilterParams struct { - Type string - Platform string - To string - Subject string - Content string - Status int +// Message represents a message log entry. +type Message struct { + To string `json:"to"` + Subject string `json:"subject,omitempty"` + Content map[string]interface{} `json:"content"` + Platform string `json:"platform"` + Template string `json:"template"` + Status uint8 `json:"status"` // 1: Sent, 2: Failed +} + +// Marshal implements the json.Marshaler interface for Message. +func (m *Message) Marshal() ([]byte, error) { + type Alias Message + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(m), + }) +} + +// Unmarshal implements the json.Unmarshaler interface for Message. +func (m *Message) Unmarshal(data []byte) error { + type Alias Message + aux := (*Alias)(m) + return json.Unmarshal(data, aux) +} + +// Traffic represents a subscription traffic log entry. +type Traffic struct { + Download int64 `json:"download"` + Upload int64 `json:"upload"` +} + +// Marshal implements the json.Marshaler interface for SubscribeTraffic. +func (s *Traffic) Marshal() ([]byte, error) { + type Alias Traffic + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(s), + }) +} + +// Unmarshal implements the json.Unmarshaler interface for SubscribeTraffic. +func (s *Traffic) Unmarshal(data []byte) error { + type Alias Traffic + aux := (*Alias)(s) + return json.Unmarshal(data, aux) +} + +// Login represents a login log entry. +type Login struct { + Method string `json:"method"` + LoginIP string `json:"login_ip"` + UserAgent string `json:"user_agent"` + Success bool `json:"success"` + Timestamp int64 `json:"timestamp"` +} + +// Marshal implements the json.Marshaler interface for Login. +func (l *Login) Marshal() ([]byte, error) { + type Alias Login + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(l), + }) +} + +// Unmarshal implements the json.Unmarshaler interface for Login. +func (l *Login) Unmarshal(data []byte) error { + type Alias Login + aux := (*Alias)(l) + return json.Unmarshal(data, aux) +} + +// Register represents a registration log entry. +type Register struct { + AuthMethod string `json:"auth_method"` + Identifier string `json:"identifier"` + RegisterIP string `json:"register_ip"` + UserAgent string `json:"user_agent"` + Timestamp int64 `json:"timestamp"` +} + +// Marshal implements the json.Marshaler interface for Register. +func (r *Register) Marshal() ([]byte, error) { + type Alias Register + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(r), + }) +} + +// Unmarshal implements the json.Unmarshaler interface for Register. + +func (r *Register) Unmarshal(data []byte) error { + type Alias Register + aux := (*Alias)(r) + return json.Unmarshal(data, aux) +} + +// Subscribe represents a subscription log entry. +type Subscribe struct { + Token string `json:"token"` + UserAgent string `json:"user_agent"` + ClientIP string `json:"client_ip"` + UserSubscribeId int64 `json:"user_subscribe_id"` +} + +// Marshal implements the json.Marshaler interface for Subscribe. +func (s *Subscribe) Marshal() ([]byte, error) { + type Alias Subscribe + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(s), + }) +} + +// Unmarshal implements the json.Unmarshaler interface for Subscribe. +func (s *Subscribe) Unmarshal(data []byte) error { + type Alias Subscribe + aux := (*Alias)(s) + return json.Unmarshal(data, aux) +} + +// ResetSubscribe represents a reset subscription log entry. +type ResetSubscribe struct { + Type uint16 `json:"type"` + UserId int64 `json:"user_id"` + OrderNo string `json:"order_no,omitempty"` + Timestamp int64 `json:"timestamp"` +} + +// Marshal implements the json.Marshaler interface for ResetSubscribe. +func (r *ResetSubscribe) Marshal() ([]byte, error) { + type Alias ResetSubscribe + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(r), + }) +} + +// Unmarshal implements the json.Unmarshaler interface for ResetSubscribe. +func (r *ResetSubscribe) Unmarshal(data []byte) error { + type Alias ResetSubscribe + aux := (*Alias)(r) + return json.Unmarshal(data, aux) +} + +// Balance represents a balance log entry. +type Balance struct { + Type uint16 `json:"type"` + Amount int64 `json:"amount"` + OrderNo string `json:"order_no,omitempty"` + Balance int64 `json:"balance"` + Timestamp int64 `json:"timestamp"` +} + +// Marshal implements the json.Marshaler interface for Balance. +func (b *Balance) Marshal() ([]byte, error) { + type Alias Balance + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(b), + }) +} + +// Unmarshal implements the json.Unmarshaler interface for Balance. +func (b *Balance) Unmarshal(data []byte) error { + type Alias Balance + aux := (*Alias)(b) + return json.Unmarshal(data, aux) +} + +// Commission represents a commission log entry. +type Commission struct { + Type uint16 `json:"type"` + Amount int64 `json:"amount"` + OrderNo string `json:"order_no"` + Timestamp int64 `json:"timestamp"` +} + +// Marshal implements the json.Marshaler interface for Commission. +func (c *Commission) Marshal() ([]byte, error) { + type Alias Commission + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(c), + }) +} + +// Unmarshal implements the json.Unmarshaler interface for Commission. +func (c *Commission) Unmarshal(data []byte) error { + type Alias Commission + aux := (*Alias)(c) + return json.Unmarshal(data, aux) +} + +// Gift represents a gift log entry. +type Gift struct { + Type uint16 `json:"type"` + OrderNo string `json:"order_no"` + SubscribeId int64 `json:"subscribe_id"` + Amount int64 `json:"amount"` + Balance int64 `json:"balance"` + Remark string `json:"remark,omitempty"` + Timestamp int64 `json:"timestamp"` +} + +// Marshal implements the json.Marshaler interface for Gift. +func (g *Gift) Marshal() ([]byte, error) { + type Alias Gift + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(g), + }) +} + +// Unmarshal implements the json.Unmarshaler interface for Gift. +func (g *Gift) Unmarshal(data []byte) error { + type Alias Gift + aux := (*Alias)(g) + return json.Unmarshal(data, aux) +} + +// UserTraffic represents a user traffic log entry. +type UserTraffic struct { + SubscribeId int64 `json:"subscribe_id"` // Subscribe ID + UserId int64 `json:"user_id"` // User ID + Upload int64 `json:"upload"` // Upload traffic in bytes + Download int64 `json:"download"` // Download traffic in bytes + Total int64 `json:"total"` // Total traffic in bytes (Upload + Download) +} + +// Marshal implements the json.Marshaler interface for UserTraffic. +func (u *UserTraffic) Marshal() ([]byte, error) { + type Alias UserTraffic + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(u), + }) +} + +// Unmarshal implements the json.Unmarshaler interface for UserTraffic. +func (u *UserTraffic) Unmarshal(data []byte) error { + type Alias UserTraffic + aux := (*Alias)(u) + return json.Unmarshal(data, aux) +} + +// UserTrafficRank represents a user traffic rank entry. +type UserTrafficRank struct { + Rank map[uint8]UserTraffic `json:"rank"` // Key is rank ,type is UserTraffic +} + +// Marshal implements the json.Marshaler interface for UserTrafficRank. +func (u *UserTrafficRank) Marshal() ([]byte, error) { + type Alias UserTrafficRank + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(u), + }) +} + +// Unmarshal implements the json.Unmarshaler interface for UserTrafficRank. +func (u *UserTrafficRank) Unmarshal(data []byte) error { + type Alias UserTrafficRank + aux := (*Alias)(u) + return json.Unmarshal(data, aux) +} + +// ServerTraffic represents a server traffic log entry. +type ServerTraffic struct { + ServerId int64 `json:"server_id"` // Server ID + Upload int64 `json:"upload"` // Upload traffic in bytes + Download int64 `json:"download"` // Download traffic in bytes + Total int64 `json:"total"` // Total traffic in bytes (Upload + Download) +} + +// Marshal implements the json.Marshaler interface for ServerTraffic. +func (s *ServerTraffic) Marshal() ([]byte, error) { + type Alias ServerTraffic + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(s), + }) +} + +// Unmarshal implements the json.Unmarshaler interface for ServerTraffic. +func (s *ServerTraffic) Unmarshal(data []byte) error { + type Alias ServerTraffic + aux := (*Alias)(s) + return json.Unmarshal(data, aux) +} + +// ServerTrafficRank represents a server traffic rank entry. +type ServerTrafficRank struct { + Rank map[uint8]ServerTraffic `json:"rank"` // Key is rank ,type is ServerTraffic +} + +// Marshal implements the json.Marshaler interface for ServerTrafficRank. +func (s *ServerTrafficRank) Marshal() ([]byte, error) { + type Alias ServerTrafficRank + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(s), + }) +} + +// Unmarshal implements the json.Unmarshaler interface for ServerTrafficRank. +func (s *ServerTrafficRank) Unmarshal(data []byte) error { + type Alias ServerTrafficRank + aux := (*Alias)(s) + return json.Unmarshal(data, aux) +} + +// TrafficStat represents a daily traffic statistics log entry. +type TrafficStat struct { + Upload int64 `json:"upload"` + Download int64 `json:"download"` + Total int64 `json:"total"` +} + +// Marshal implements the json.Marshaler interface for TrafficStat. +func (t *TrafficStat) Marshal() ([]byte, error) { + type Alias TrafficStat + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(t), + }) +} + +// Unmarshal implements the json.Unmarshaler interface for TrafficStat. +func (t *TrafficStat) Unmarshal(data []byte) error { + type Alias TrafficStat + aux := (*Alias)(t) + return json.Unmarshal(data, aux) } diff --git a/internal/model/log/model.go b/internal/model/log/model.go index 8bacf15..783b6b8 100644 --- a/internal/model/log/model.go +++ b/internal/model/log/model.go @@ -1,9 +1,63 @@ package log import ( + "context" + "gorm.io/gorm" ) -func NewModel(conn *gorm.DB) Model { - return newLogModel(conn) +func NewModel(db *gorm.DB) Model { + return &customSystemLogModel{ + defaultLogModel: newSystemLogModel(db), + } +} + +type FilterParams struct { + Page int + Size int + Type uint8 + Data string + Search string + ObjectID int64 +} + +type customSystemLogLogicModel interface { + FilterSystemLog(ctx context.Context, filter *FilterParams) ([]*SystemLog, int64, error) +} + +func (m *customSystemLogModel) FilterSystemLog(ctx context.Context, filter *FilterParams) ([]*SystemLog, int64, error) { + tx := m.WithContext(ctx).Model(&SystemLog{}).Order("id DESC") + if filter == nil { + filter = &FilterParams{ + Page: 1, + Size: 10, + } + } + + if filter.Page < 1 { + filter.Page = 1 + } + if filter.Size < 1 { + filter.Size = 10 + } + + if filter.Type != 0 { + tx = tx.Where("`type` = ?", filter.Type) + } + + if filter.Data != "" { + tx = tx.Where("`date` = ?", filter.Data) + } + + if filter.ObjectID != 0 { + tx = tx.Where("`object_id` = ?", filter.ObjectID) + } + if filter.Search != "" { + tx = tx.Where("`content` LIKE ?", "%"+filter.Search+"%") + } + + var total int64 + var logs []*SystemLog + err := tx.Count(&total).Limit(filter.Size).Offset((filter.Page - 1) * filter.Size).Find(&logs).Error + return logs, total, err } diff --git a/internal/model/node/cache.go b/internal/model/node/cache.go new file mode 100644 index 0000000..3d16c78 --- /dev/null +++ b/internal/model/node/cache.go @@ -0,0 +1,163 @@ +package node + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/pkg/errors" + "github.com/redis/go-redis/v9" +) + +type ( + customCacheLogicModel interface { + StatusCache(ctx context.Context, serverId int64) (Status, error) + UpdateStatusCache(ctx context.Context, serverId int64, status *Status) error + OnlineUserSubscribe(ctx context.Context, serverId int64, protocol string) (OnlineUserSubscribe, error) + UpdateOnlineUserSubscribe(ctx context.Context, serverId int64, protocol string, subscribe OnlineUserSubscribe) error + OnlineUserSubscribeGlobal(ctx context.Context) (int64, error) + UpdateOnlineUserSubscribeGlobal(ctx context.Context, subscribe OnlineUserSubscribe) error + } + + Status struct { + Cpu float64 `json:"cpu"` + Mem float64 `json:"mem"` + Disk float64 `json:"disk"` + UpdatedAt int64 `json:"updated_at"` + } + + OnlineUserSubscribe map[int64][]string +) + +// Marshal to json string +func (s *Status) Marshal() string { + type Alias Status + data, _ := json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(s), + }) + return string(data) +} + +// Unmarshal from json string +func (s *Status) Unmarshal(data string) error { + type Alias Status + aux := &struct { + *Alias + }{ + Alias: (*Alias)(s), + } + return json.Unmarshal([]byte(data), &aux) +} + +const ( + Expiry = 300 * time.Second // Cache expiry time in seconds + StatusCacheKey = "node:status:%d" // Node status cache key format (Server ID and protocol) Example: node:status:1:shadowsocks + OnlineUserCacheKeyWithSubscribe = "node:online:subscribe:%d:%s" // Online user subscribe cache key format (Server ID and protocol) Example: node:online:subscribe:1:shadowsocks + OnlineUserSubscribeCacheKeyWithGlobal = "node:online:subscribe:global" // Online user global subscribe cache key +) + +// UpdateStatusCache Update server status to cache +func (m *customServerModel) UpdateStatusCache(ctx context.Context, serverId int64, status *Status) error { + key := fmt.Sprintf(StatusCacheKey, serverId) + return m.Cache.Set(ctx, key, status.Marshal(), Expiry).Err() + +} + +// DeleteStatusCache Delete server status from cache +func (m *customServerModel) DeleteStatusCache(ctx context.Context, serverId int64) error { + key := fmt.Sprintf(StatusCacheKey, serverId) + return m.Cache.Del(ctx, key).Err() +} + +// StatusCache Get server status from cache +func (m *customServerModel) StatusCache(ctx context.Context, serverId int64) (Status, error) { + var status Status + key := fmt.Sprintf(StatusCacheKey, serverId) + + result, err := m.Cache.Get(ctx, key).Result() + if err != nil { + if errors.Is(err, redis.Nil) { + return status, nil + } + return status, err + } + if result == "" { + return status, nil + } + err = status.Unmarshal(result) + return status, err +} + +// OnlineUserSubscribe Get online user subscribe +func (m *customServerModel) OnlineUserSubscribe(ctx context.Context, serverId int64, protocol string) (OnlineUserSubscribe, error) { + key := fmt.Sprintf(OnlineUserCacheKeyWithSubscribe, serverId, protocol) + result, err := m.Cache.Get(ctx, key).Result() + if err != nil { + if errors.Is(err, redis.Nil) { + return OnlineUserSubscribe{}, nil + } + return nil, err + } + if result == "" { + return OnlineUserSubscribe{}, nil + } + var subscribe OnlineUserSubscribe + err = json.Unmarshal([]byte(result), &subscribe) + return subscribe, err +} + +// UpdateOnlineUserSubscribe Update online user subscribe +func (m *customServerModel) UpdateOnlineUserSubscribe(ctx context.Context, serverId int64, protocol string, subscribe OnlineUserSubscribe) error { + key := fmt.Sprintf(OnlineUserCacheKeyWithSubscribe, serverId, protocol) + data, err := json.Marshal(subscribe) + if err != nil { + return err + } + return m.Cache.Set(ctx, key, data, Expiry).Err() +} + +// DeleteOnlineUserSubscribe Delete online user subscribe +func (m *customServerModel) DeleteOnlineUserSubscribe(ctx context.Context, serverId int64, protocol string) error { + key := fmt.Sprintf(OnlineUserCacheKeyWithSubscribe, serverId, protocol) + return m.Cache.Del(ctx, key).Err() +} + +// OnlineUserSubscribeGlobal Get global online user subscribe count +func (m *customServerModel) OnlineUserSubscribeGlobal(ctx context.Context) (int64, error) { + now := time.Now().Unix() + // Clear expired data + if err := m.Cache.ZRemRangeByScore(ctx, OnlineUserSubscribeCacheKeyWithGlobal, "-inf", fmt.Sprintf("%d", now)).Err(); err != nil { + return 0, err + } + return m.Cache.ZCard(ctx, OnlineUserSubscribeCacheKeyWithGlobal).Result() +} + +// UpdateOnlineUserSubscribeGlobal Update global online user subscribe count +func (m *customServerModel) UpdateOnlineUserSubscribeGlobal(ctx context.Context, subscribe OnlineUserSubscribe) error { + now := time.Now() + expireTime := now.Add(5 * time.Minute).Unix() // set expire time 5 minutes later + + pipe := m.Cache.Pipeline() + + // Clear expired data + pipe.ZRemRangeByScore(ctx, OnlineUserSubscribeCacheKeyWithGlobal, "-inf", fmt.Sprintf("%d", now.Unix())) + // Add or update each subscribe with new expire time + for sub := range subscribe { + // Use ZAdd to add or update the member with new score (expire time) + pipe.ZAdd(ctx, OnlineUserSubscribeCacheKeyWithGlobal, redis.Z{ + Score: float64(expireTime), + Member: sub, + }) + } + + _, err := pipe.Exec(ctx) + return err +} + +// DeleteOnlineUserSubscribeGlobal Delete global online user subscribe count +func (m *customServerModel) DeleteOnlineUserSubscribeGlobal(ctx context.Context) error { + return m.Cache.Del(ctx, OnlineUserSubscribeCacheKeyWithGlobal).Err() +} diff --git a/internal/model/node/default.go b/internal/model/node/default.go new file mode 100644 index 0000000..575ea5f --- /dev/null +++ b/internal/model/node/default.go @@ -0,0 +1,131 @@ +package node + +import ( + "context" + + "github.com/redis/go-redis/v9" + "gorm.io/gorm" +) + +var _ Model = (*customServerModel)(nil) + +//goland:noinspection GoNameStartsWithPackageName +type ( + Model interface { + serverModel + NodeModel + customCacheLogicModel + customServerLogicModel + } + serverModel interface { + InsertServer(ctx context.Context, data *Server, tx ...*gorm.DB) error + FindOneServer(ctx context.Context, id int64) (*Server, error) + UpdateServer(ctx context.Context, data *Server, 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 + } + + NodeModel interface { + InsertNode(ctx context.Context, data *Node, tx ...*gorm.DB) error + FindOneNode(ctx context.Context, id int64) (*Node, error) + UpdateNode(ctx context.Context, data *Node, tx ...*gorm.DB) error + DeleteNode(ctx context.Context, id int64, tx ...*gorm.DB) error + } + + customServerModel struct { + *defaultServerModel + } + defaultServerModel struct { + *gorm.DB + Cache *redis.Client + } +) + +func newServerModel(db *gorm.DB, cache *redis.Client) *defaultServerModel { + return &defaultServerModel{ + DB: db, + Cache: cache, + } +} + +// NewModel returns a model for the database table. +func NewModel(conn *gorm.DB, cache *redis.Client) Model { + return &customServerModel{ + defaultServerModel: newServerModel(conn, cache), + } +} + +func (m *defaultServerModel) InsertServer(ctx context.Context, data *Server, tx ...*gorm.DB) error { + db := m.DB + if len(tx) > 0 { + db = tx[0] + } + return db.WithContext(ctx).Create(data).Error +} + +func (m *defaultServerModel) FindOneServer(ctx context.Context, id int64) (*Server, error) { + var server Server + err := m.WithContext(ctx).Model(&Server{}).Where("id = ?", id).First(&server).Error + return &server, err +} + +func (m *defaultServerModel) UpdateServer(ctx context.Context, data *Server, tx ...*gorm.DB) error { + _, err := m.FindOneServer(ctx, data.Id) + if err != nil { + return err + } + + db := m.DB + if len(tx) > 0 { + db = tx[0] + } + return db.WithContext(ctx).Where("`id` = ?", data.Id).Save(data).Error + +} + +func (m *defaultServerModel) DeleteServer(ctx context.Context, id int64, tx ...*gorm.DB) error { + db := m.DB + if len(tx) > 0 { + db = tx[0] + } + return db.WithContext(ctx).Where("`id` = ?", id).Delete(&Server{}).Error +} + +func (m *defaultServerModel) InsertNode(ctx context.Context, data *Node, tx ...*gorm.DB) error { + db := m.DB + if len(tx) > 0 { + db = tx[0] + } + return db.WithContext(ctx).Create(data).Error +} + +func (m *defaultServerModel) FindOneNode(ctx context.Context, id int64) (*Node, error) { + var node Node + err := m.WithContext(ctx).Model(&Node{}).Where("id = ?", id).First(&node).Error + return &node, err +} + +func (m *defaultServerModel) UpdateNode(ctx context.Context, data *Node, tx ...*gorm.DB) error { + _, err := m.FindOneNode(ctx, data.Id) + if err != nil { + return err + } + + db := m.DB + if len(tx) > 0 { + db = tx[0] + } + return db.WithContext(ctx).Where("`id` = ?", data.Id).Save(data).Error +} + +func (m *defaultServerModel) DeleteNode(ctx context.Context, id int64, tx ...*gorm.DB) error { + db := m.DB + if len(tx) > 0 { + db = tx[0] + } + return db.WithContext(ctx).Where("`id` = ?", id).Delete(&Node{}).Error +} + +func (m *defaultServerModel) Transaction(ctx context.Context, fn func(db *gorm.DB) error) error { + return m.WithContext(ctx).Transaction(fn) +} diff --git a/internal/model/node/model.go b/internal/model/node/model.go new file mode 100644 index 0000000..30eba18 --- /dev/null +++ b/internal/model/node/model.go @@ -0,0 +1,185 @@ +package node + +import ( + "context" + "fmt" + "strings" + + "github.com/perfect-panel/server/pkg/tool" + "gorm.io/gorm" +) + +type customServerLogicModel interface { + FilterServerList(ctx context.Context, params *FilterParams) (int64, []*Server, error) + FilterNodeList(ctx context.Context, params *FilterNodeParams) (int64, []*Node, error) + ClearNodeCache(ctx context.Context, params *FilterNodeParams) error +} + +const ( + // ServerUserListCacheKey Server User List Cache Key + ServerUserListCacheKey = "server:user:" + + // ServerConfigCacheKey Server Config Cache Key + ServerConfigCacheKey = "server:config:" +) + +// FilterParams Filter Server Params +type FilterParams struct { + Page int + Size int + Ids []int64 // Server IDs + Search string +} + +type FilterNodeParams struct { + Page int // Page Number + Size int // Page Size + NodeId []int64 // Node IDs + ServerId []int64 // Server IDs + Tag []string // Tags + Search string // Search Address or Name + Protocol string // Protocol + Preload bool // Preload Server + Enabled *bool // Enabled +} + +// FilterServerList Filter Server List +func (m *customServerModel) FilterServerList(ctx context.Context, params *FilterParams) (int64, []*Server, error) { + var servers []*Server + var total int64 + query := m.WithContext(ctx).Model(&Server{}) + if params == nil { + params = &FilterParams{ + Page: 1, + Size: 10, + } + } + if params.Search != "" { + s := "%" + params.Search + "%" + query = query.Where("`name` LIKE ? OR `address` LIKE ?", s, s) + } + if len(params.Ids) > 0 { + query = query.Where("id IN ?", params.Ids) + } + err := query.Count(&total).Order("sort ASC").Limit(params.Size).Offset((params.Page - 1) * params.Size).Find(&servers).Error + return total, servers, err +} + +// FilterNodeList Filter Node List +func (m *customServerModel) FilterNodeList(ctx context.Context, params *FilterNodeParams) (int64, []*Node, error) { + var nodes []*Node + var total int64 + query := m.WithContext(ctx).Model(&Node{}) + if params == nil { + params = &FilterNodeParams{ + Page: 1, + Size: 10, + } + } + if params.Search != "" { + s := "%" + params.Search + "%" + query = query.Where("`name` LIKE ? OR `address` LIKE ? OR `tags` LIKE ? OR `port` LIKE ? ", s, s, s, s) + } + if len(params.NodeId) > 0 { + query = query.Where("id IN ?", params.NodeId) + } + if len(params.ServerId) > 0 { + query = query.Where("server_id IN ?", params.ServerId) + } + if len(params.Tag) > 0 { + query = query.Scopes(InSet("tags", params.Tag)) + } + if params.Protocol != "" { + query = query.Where("protocol = ?", params.Protocol) + } + + if params.Enabled != nil { + query = query.Where("enabled = ?", *params.Enabled) + } + + if params.Preload { + query = query.Preload("Server") + } + + err := query.Count(&total).Order("sort ASC").Limit(params.Size).Offset((params.Page - 1) * params.Size).Find(&nodes).Error + return total, nodes, err +} + +// ClearNodeCache Clear Node Cache +func (m *customServerModel) ClearNodeCache(ctx context.Context, params *FilterNodeParams) error { + _, nodes, err := m.FilterNodeList(ctx, params) + if err != nil { + return err + } + var cacheKeys []string + for _, node := range nodes { + cacheKeys = append(cacheKeys, fmt.Sprintf("%s%d", ServerUserListCacheKey, node.ServerId)) + if node.Protocol != "" { + var cursor uint64 + for { + keys, newCursor, err := m.Cache.Scan(ctx, cursor, fmt.Sprintf("%s%d*", ServerConfigCacheKey, node.ServerId), 100).Result() + if err != nil { + return err + } + if len(keys) > 0 { + cacheKeys = append(keys, keys...) + } + cursor = newCursor + if cursor == 0 { + break + } + } + } + } + + if len(cacheKeys) > 0 { + cacheKeys = tool.RemoveDuplicateElements(cacheKeys...) + return m.Cache.Del(ctx, cacheKeys...).Err() + } + return nil +} + +// ClearServerCache Clear Server Cache +func (m *customServerModel) ClearServerCache(ctx context.Context, serverId int64) error { + var cacheKeys []string + cacheKeys = append(cacheKeys, fmt.Sprintf("%s%d", ServerUserListCacheKey, serverId)) + var cursor uint64 + for { + keys, newCursor, err := m.Cache.Scan(ctx, 0, fmt.Sprintf("%s%d*", ServerConfigCacheKey, serverId), 100).Result() + if err != nil { + return err + } + if len(keys) > 0 { + cacheKeys = append(cacheKeys, keys...) + } + cursor = newCursor + if cursor == 0 { + break + } + } + + if len(cacheKeys) > 0 { + cacheKeys = tool.RemoveDuplicateElements(cacheKeys...) + return m.Cache.Del(ctx, cacheKeys...).Err() + } + 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...) + } +} diff --git a/internal/model/node/node.go b/internal/model/node/node.go new file mode 100644 index 0000000..89d665d --- /dev/null +++ b/internal/model/node/node.go @@ -0,0 +1,82 @@ +package node + +import ( + "time" + + "github.com/perfect-panel/server/pkg/logger" + "gorm.io/gorm" +) + +type Node struct { + Id int64 `gorm:"primary_key"` + Name string `gorm:"type:varchar(100);not null;default:'';comment:Node Name"` + Tags string `gorm:"type:varchar(255);not null;default:'';comment:Tags"` + Port uint16 `gorm:"not null;default:0;comment:Connect Port"` + Address string `gorm:"type:varchar(255);not null;default:'';comment:Connect Address"` + ServerId int64 `gorm:"not null;default:0;comment:Server ID"` + Server *Server `gorm:"foreignKey:ServerId;references:Id"` + Protocol string `gorm:"type:varchar(100);not null;default:'';comment:Protocol"` + Enabled *bool `gorm:"type:boolean;not null;default:true;comment:Enabled"` + Sort int `gorm:"uniqueIndex;not null;default:0;comment:Sort"` + CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` + UpdatedAt time.Time `gorm:"comment:Update Time"` +} + +func (n *Node) TableName() string { + return "nodes" +} + +func (n *Node) BeforeCreate(tx *gorm.DB) error { + if n.Sort == 0 { + var maxSort int + if err := tx.Model(&Node{}).Select("COALESCE(MAX(sort), 0)").Scan(&maxSort).Error; err != nil { + return err + } + n.Sort = maxSort + 1 + } + return nil +} + +func (n *Node) BeforeDelete(tx *gorm.DB) error { + if err := tx.Exec("UPDATE `nodes` SET sort = sort - 1 WHERE sort > ?", n.Sort).Error; err != nil { + return err + } + return nil +} + +func (n *Node) BeforeUpdate(tx *gorm.DB) error { + var count int64 + if err := tx.Set("gorm:query_option", "FOR UPDATE").Model(&Server{}). + Where("sort = ? AND id != ?", n.Sort, n.Id).Count(&count).Error; err != nil { + return err + } + if count > 1 { + // reorder sort + if err := reorderSortWithNode(tx); err != nil { + logger.Errorf("[Server] BeforeUpdate reorderSort error: %v", err.Error()) + return err + } + // get max sort + var maxSort int + if err := tx.Model(&Server{}).Select("MAX(sort)").Scan(&maxSort).Error; err != nil { + return err + } + n.Sort = maxSort + 1 + } + return nil +} + +func reorderSortWithNode(tx *gorm.DB) error { + var nodes []Node + if err := tx.Order("sort, id").Find(&nodes).Error; err != nil { + return err + } + for i, node := range nodes { + if node.Sort != i+1 { + if err := tx.Exec("UPDATE `nodes` SET sort = ? WHERE id = ?", i+1, node.Id).Error; err != nil { + return err + } + } + } + return nil +} diff --git a/internal/model/node/server.go b/internal/model/node/server.go new file mode 100644 index 0000000..00e433e --- /dev/null +++ b/internal/model/node/server.go @@ -0,0 +1,188 @@ +package node + +import ( + "encoding/json" + "time" + + "github.com/perfect-panel/server/pkg/logger" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +type Server struct { + Id int64 `gorm:"primary_key"` + Name string `gorm:"type:varchar(100);not null;default:'';comment:Server Name"` + Country string `gorm:"type:varchar(128);not null;default:'';comment:Country"` + City string `gorm:"type:varchar(128);not null;default:'';comment:City"` + //Ratio float32 `gorm:"type:DECIMAL(4,2);not null;default:0;comment:Traffic Ratio"` + Address string `gorm:"type:varchar(100);not null;default:'';comment:Server Address"` + Sort int `gorm:"type:int;not null;default:0;comment:Sort"` + Protocols string `gorm:"type:text;default:null;comment:Protocol"` + LastReportedAt *time.Time `gorm:"comment:Last Reported Time"` + CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` + UpdatedAt time.Time `gorm:"comment:Update Time"` +} + +func (*Server) TableName() string { + return "servers" +} + +func (m *Server) BeforeCreate(tx *gorm.DB) error { + if m.Sort == 0 { + var maxSort int + if err := tx.Model(&Server{}).Select("COALESCE(MAX(sort), 0)").Scan(&maxSort).Error; err != nil { + return err + } + m.Sort = maxSort + 1 + } + return nil +} + +func (m *Server) BeforeDelete(tx *gorm.DB) error { + if err := tx.Exec("UPDATE `servers` SET sort = sort - 1 WHERE sort > ?", m.Sort).Error; err != nil { + return err + } + return nil +} + +func (m *Server) BeforeUpdate(tx *gorm.DB) error { + var count int64 + if err := tx.Set("gorm:query_option", "FOR UPDATE").Model(&Server{}). + Where("sort = ? AND id != ?", m.Sort, m.Id).Count(&count).Error; err != nil { + return err + } + if count > 1 { + // reorder sort + if err := reorderSortWithServer(tx); err != nil { + logger.Errorf("[Server] BeforeUpdate reorderSort error: %v", err.Error()) + return err + } + // get max sort + var maxSort int + if err := tx.Model(&Server{}).Select("MAX(sort)").Scan(&maxSort).Error; err != nil { + return err + } + m.Sort = maxSort + 1 + } + return nil +} + +// MarshalProtocols Marshal server protocols to json +func (m *Server) MarshalProtocols(list []Protocol) error { + var validate = make(map[string]bool) + for _, protocol := range list { + if protocol.Type == "" { + return errors.New("protocol type is required") + } + if _, exists := validate[protocol.Type]; exists { + return errors.New("duplicate protocol type: " + protocol.Type) + } + validate[protocol.Type] = true + } + data, err := json.Marshal(list) + if err != nil { + return err + } + m.Protocols = string(data) + return nil +} + +// UnmarshalProtocols Unmarshal server protocols from json +func (m *Server) UnmarshalProtocols() ([]Protocol, error) { + var list []Protocol + if m.Protocols == "" { + return list, nil + } + err := json.Unmarshal([]byte(m.Protocols), &list) + if err != nil { + return nil, err + } + return list, nil +} + +type Protocol struct { + Type string `json:"type"` + Port uint16 `json:"port"` + Enable bool `json:"enable"` + Security string `json:"security,omitempty"` + SNI string `json:"sni,omitempty"` + AllowInsecure bool `json:"allow_insecure,omitempty"` + Fingerprint string `json:"fingerprint,omitempty"` + RealityServerAddr string `json:"reality_server_addr,omitempty"` + RealityServerPort int `json:"reality_server_port,omitempty"` + RealityPrivateKey string `json:"reality_private_key,omitempty"` + RealityPublicKey string `json:"reality_public_key,omitempty"` + RealityShortId string `json:"reality_short_id,omitempty"` + Transport string `json:"transport,omitempty"` + Host string `json:"host,omitempty"` + Path string `json:"path,omitempty"` + ServiceName string `json:"service_name,omitempty"` + Cipher string `json:"cipher,omitempty"` + ServerKey string `json:"server_key,omitempty"` + Flow string `json:"flow,omitempty"` + HopPorts string `json:"hop_ports,omitempty"` + HopInterval int `json:"hop_interval,omitempty"` + ObfsPassword string `json:"obfs_password,omitempty"` + DisableSNI bool `json:"disable_sni,omitempty"` + ReduceRtt bool `json:"reduce_rtt,omitempty"` + UDPRelayMode string `json:"udp_relay_mode,omitempty"` + CongestionController string `json:"congestion_controller,omitempty"` + Multiplex string `json:"multiplex,omitempty"` // mux, eg: off/low/medium/high + PaddingScheme string `json:"padding_scheme,omitempty"` // padding scheme + UpMbps int `json:"up_mbps,omitempty"` // upload speed limit + DownMbps int `json:"down_mbps,omitempty"` // download speed limit + Obfs string `json:"obfs,omitempty"` // obfs, 'none', 'http', 'tls' + ObfsHost string `json:"obfs_host,omitempty"` // obfs host + ObfsPath string `json:"obfs_path,omitempty"` // obfs path + XhttpMode string `json:"xhttp_mode,omitempty"` // xhttp mode + XhttpExtra string `json:"xhttp_extra,omitempty"` // xhttp extra path + Encryption string `json:"encryption,omitempty"` // encryption,'none', 'mlkem768x25519plus' + EncryptionMode string `json:"encryption_mode,omitempty"` // encryption mode,'native', 'xorpub', 'random' + EncryptionRtt string `json:"encryption_rtt,omitempty"` // encryption rtt,'0rtt', '1rtt' + EncryptionTicket string `json:"encryption_ticket,omitempty"` // encryption ticket + EncryptionServerPadding string `json:"encryption_server_padding,omitempty"` // encryption server padding + EncryptionPrivateKey string `json:"encryption_private_key,omitempty"` // encryption private key + EncryptionClientPadding string `json:"encryption_client_padding,omitempty"` // encryption client padding + EncryptionPassword string `json:"encryption_password,omitempty"` // encryption password + + Ratio float64 `json:"ratio,omitempty"` // Traffic ratio, default is 1 + CertMode string `json:"cert_mode,omitempty"` // Certificate mode, `none`|`http`|`dns`|`self` + CertDNSProvider string `json:"cert_dns_provider,omitempty"` // DNS provider for certificate + CertDNSEnv string `json:"cert_dns_env"` // Environment for DNS provider +} + +// Marshal protocol to json +func (m *Protocol) Marshal() ([]byte, error) { + type Alias Protocol + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(m), + }) +} + +// Unmarshal json to protocol +func (m *Protocol) Unmarshal(data []byte) error { + type Alias Protocol + aux := &struct { + *Alias + }{ + Alias: (*Alias)(m), + } + return json.Unmarshal(data, &aux) +} + +func reorderSortWithServer(tx *gorm.DB) error { + var servers []Server + if err := tx.Order("sort, id").Find(&servers).Error; err != nil { + return err + } + for i, server := range servers { + if server.Sort != i+1 { + if err := tx.Exec("UPDATE `servers` SET sort = ? WHERE id = ?", i+1, server.Id).Error; err != nil { + return err + } + } + } + return nil +} diff --git a/internal/model/order/default.go b/internal/model/order/default.go index 6f83585..a59eeb0 100644 --- a/internal/model/order/default.go +++ b/internal/model/order/default.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/perfect-panel/ppanel-server/pkg/cache" + "github.com/perfect-panel/server/pkg/cache" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) diff --git a/internal/model/order/model.go b/internal/model/order/model.go index 9909d80..d98e361 100644 --- a/internal/model/order/model.go +++ b/internal/model/order/model.go @@ -4,9 +4,9 @@ import ( "context" "time" - "github.com/perfect-panel/ppanel-server/internal/model/payment" + "github.com/perfect-panel/server/internal/model/payment" - "github.com/perfect-panel/ppanel-server/internal/model/subscribe" + "github.com/perfect-panel/server/internal/model/subscribe" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) @@ -40,6 +40,13 @@ type Details struct { UpdatedAt time.Time `gorm:"comment:Update Time"` } +type OrdersTotalWithDate struct { + Date string + AmountTotal int64 + NewOrderAmount int64 + RenewalOrderAmount int64 +} + type customOrderLogicModel interface { UpdateOrderStatus(ctx context.Context, orderNo string, status uint8, tx ...*gorm.DB) error QueryOrderListByPage(ctx context.Context, page, size int, status uint8, user, subscribe int64, search string) (int64, []*Details, error) @@ -47,11 +54,19 @@ type customOrderLogicModel interface { FindOneDetailsByOrderNo(ctx context.Context, orderNo string) (*Details, error) QueryMonthlyOrders(ctx context.Context, date time.Time) (OrdersTotal, error) QueryDateOrders(ctx context.Context, date time.Time) (OrdersTotal, error) - QueryPendingOrders(ctx context.Context) ([]*Order, error) QueryTotalOrders(ctx context.Context) (OrdersTotal, error) QueryMonthlyUserCounts(ctx context.Context, date time.Time) (int64, int64, error) QueryDateUserCounts(ctx context.Context, date time.Time) (int64, int64, error) + QueryTotalUserCounts(ctx context.Context) (int64, int64, error) IsUserEligibleForNewOrder(ctx context.Context, userID int64) (bool, error) + QueryDailyOrdersList(ctx context.Context, date time.Time) ([]OrdersTotalWithDate, error) + QueryMonthlyOrdersList(ctx context.Context, date time.Time) ([]OrdersTotalWithDate, error) +} + +// UserCounts User counts for new and renewal users +type UserCounts struct { + NewUsers int64 `gorm:"column:new_users"` + RenewalUsers int64 `gorm:"column:renewal_users"` } // NewModel returns a model for the database table. @@ -156,51 +171,78 @@ func (m *customOrderModel) QueryDateOrders(ctx context.Context, date time.Time) func (m *customOrderModel) QueryTotalOrders(ctx context.Context) (OrdersTotal, error) { var result OrdersTotal - err := m.QueryNoCacheCtx(ctx, &result, func(conn *gorm.DB, v interface{}) error { + + err := m.QueryNoCacheCtx(ctx, &result, func(conn *gorm.DB, _ interface{}) error { return conn.Model(&Order{}). + Select(` + SUM(amount) AS amount_total, + SUM(CASE WHEN is_new = 1 THEN amount ELSE 0 END) AS new_order_amount, + SUM(CASE WHEN is_new = 0 THEN amount ELSE 0 END) AS renewal_order_amount + `). Where("status IN ? AND method != ?", []int64{2, 5}, "balance"). - Select( - "SUM(amount) as amount_total, " + - "SUM(CASE WHEN is_new = 1 THEN amount ELSE 0 END) as new_order_amount, " + - "SUM(CASE WHEN is_new = 0 THEN amount ELSE 0 END) as renewal_order_amount", - ). - Scan(v).Error + Scan(&result).Error }) + return result, err } func (m *customOrderModel) QueryMonthlyUserCounts(ctx context.Context, date time.Time) (int64, int64, error) { + // 获取当月第一天零点 firstDay := time.Date(date.Year(), date.Month(), 1, 0, 0, 0, 0, date.Location()) - lastDay := firstDay.AddDate(0, 1, -1) + // 获取下个月第一天零点(避免漏掉最后一天的订单) + nextMonth := firstDay.AddDate(0, 1, 0) - var newUsers int64 - var renewalUsers int64 + var counts UserCounts + + // 执行查询 err := m.QueryNoCacheCtx(ctx, nil, func(conn *gorm.DB, _ interface{}) error { return conn.Model(&Order{}). - Where("status IN ? AND created_at BETWEEN ? AND ? AND method != ?", []int64{2, 5}, firstDay, lastDay, "balance"). - Select( - "COUNT(DISTINCT CASE WHEN is_new = 1 THEN user_id END) as new_users, "+ - "COUNT(DISTINCT CASE WHEN is_new = 0 THEN user_id END) as renewal_users"). - Row().Scan(&newUsers, &renewalUsers) + Select(` + COUNT(DISTINCT CASE WHEN is_new = 1 THEN user_id END) AS new_users, + COUNT(DISTINCT CASE WHEN is_new = 0 THEN user_id END) AS renewal_users + `). + Where("status IN ? AND created_at >= ? AND created_at < ? AND method != ?", + []int64{2, 5}, firstDay, nextMonth, "balance"). + Scan(&counts).Error }) - return newUsers, renewalUsers, err + + return counts.NewUsers, counts.RenewalUsers, err } - func (m *customOrderModel) QueryDateUserCounts(ctx context.Context, date time.Time) (int64, int64, error) { - start := date.Truncate(24 * time.Hour) - end := start.Add(24 * time.Hour).Add(-time.Nanosecond) + // 当天 00:00:00 + start := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location()) + // 下一天 00:00:00 + nextDay := start.Add(24 * time.Hour) + + var counts UserCounts - var newUsers int64 - var renewalUsers int64 err := m.QueryNoCacheCtx(ctx, nil, func(conn *gorm.DB, _ interface{}) error { return conn.Model(&Order{}). - Where("status IN ? AND created_at BETWEEN ? AND ? AND method != ?", []int64{2, 5}, start, end, "balance"). - Select( - "COUNT(DISTINCT CASE WHEN is_new = 1 THEN user_id END) as new_users, "+ - "COUNT(DISTINCT CASE WHEN is_new = 0 THEN user_id END) as renewal_users"). - Row().Scan(&newUsers, &renewalUsers) + Select(` + COUNT(DISTINCT CASE WHEN is_new = 1 THEN user_id END) AS new_users, + COUNT(DISTINCT CASE WHEN is_new = 0 THEN user_id END) AS renewal_users + `). + Where("status IN ? AND created_at >= ? AND created_at < ? AND method != ?", + []int64{2, 5}, start, nextDay, "balance"). + Scan(&counts).Error }) - return newUsers, renewalUsers, err + + return counts.NewUsers, counts.RenewalUsers, err +} +func (m *customOrderModel) QueryTotalUserCounts(ctx context.Context) (int64, int64, error) { + var counts UserCounts + + err := m.QueryNoCacheCtx(ctx, nil, func(conn *gorm.DB, _ interface{}) error { + return conn.Model(&Order{}). + Where("status IN ? AND method != ?", []int64{2, 5}, "balance"). + Select(` + COUNT(DISTINCT CASE WHEN is_new = 1 THEN user_id END) AS new_users, + COUNT(DISTINCT CASE WHEN is_new = 0 THEN user_id END) AS renewal_users + `). + Scan(&counts).Error + }) + + return counts.NewUsers, counts.RenewalUsers, err } func (m *customOrderModel) IsUserEligibleForNewOrder(ctx context.Context, userID int64) (bool, error) { @@ -213,12 +255,54 @@ func (m *customOrderModel) IsUserEligibleForNewOrder(ctx context.Context, userID return count == 0, err } -func (m *customOrderModel) QueryPendingOrders(ctx context.Context) ([]*Order, error) { - var orderInfo []*Order - err := m.QueryNoCacheCtx(ctx, &orderInfo, func(conn *gorm.DB, v interface{}) error { +// QueryDailyOrdersList 查询当月每日订单统计 +func (m *customOrderModel) QueryDailyOrdersList(ctx context.Context, date time.Time) ([]OrdersTotalWithDate, error) { + var results []OrdersTotalWithDate + + err := m.QueryNoCacheCtx(ctx, &results, func(conn *gorm.DB, v interface{}) error { + // 当月 1 号 00:00:00 + firstDay := time.Date(date.Year(), date.Month(), 1, 0, 0, 0, 0, date.Location()) + // 第二天 00:00:00 + nextDay := date.AddDate(0, 0, 1).Truncate(24 * time.Hour) + return conn.Model(&Order{}). - Where("status = ?", 1). - Find(v).Error + Select(` + DATE_FORMAT(created_at, '%Y-%m-%d') AS date, + SUM(amount) AS amount_total, + SUM(CASE WHEN is_new = 1 THEN amount ELSE 0 END) AS new_order_amount, + SUM(CASE WHEN is_new = 0 THEN amount ELSE 0 END) AS renewal_order_amount + `). + Where("status IN ? AND created_at >= ? AND created_at < ? AND method != ?", + []int64{2, 5}, firstDay, nextDay, "balance"). + Group("DATE_FORMAT(created_at, '%Y-%m-%d')"). + Order("date ASC"). + Scan(v).Error }) - return orderInfo, err + return results, err +} + +// QueryMonthlyOrdersList 查询过去 6 个月订单统计(包含当前月) +func (m *customOrderModel) QueryMonthlyOrdersList(ctx context.Context, date time.Time) ([]OrdersTotalWithDate, error) { + var results []OrdersTotalWithDate + + err := m.QueryNoCacheCtx(ctx, &results, func(conn *gorm.DB, v interface{}) error { + // 六个月前(取月初) + start := time.Date(date.Year(), date.Month(), 1, 0, 0, 0, 0, date.Location()).AddDate(0, -5, 0) + // 下个月月初 + end := time.Date(date.Year(), date.Month(), 1, 0, 0, 0, 0, date.Location()).AddDate(0, 1, 0) + + return conn.Model(&Order{}). + Select(` + DATE_FORMAT(created_at, '%Y-%m') AS date, + SUM(amount) AS amount_total, + SUM(CASE WHEN is_new = 1 THEN amount ELSE 0 END) AS new_order_amount, + SUM(CASE WHEN is_new = 0 THEN amount ELSE 0 END) AS renewal_order_amount + `). + Where("status IN ? AND created_at >= ? AND created_at < ? AND method != ?", + []int64{2, 5}, start, end, "balance"). + Group("DATE_FORMAT(created_at, '%Y-%m')"). + Order("date ASC"). + Scan(v).Error + }) + return results, err } diff --git a/internal/model/payment/default.go b/internal/model/payment/default.go index cd4afdd..0ebd891 100644 --- a/internal/model/payment/default.go +++ b/internal/model/payment/default.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/perfect-panel/ppanel-server/pkg/cache" + "github.com/perfect-panel/server/pkg/cache" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) @@ -22,10 +22,10 @@ type ( customPaymentLogicModel } paymentModel interface { - Insert(ctx context.Context, data *Payment) error + Insert(ctx context.Context, data *Payment, tx ...*gorm.DB) error FindOne(ctx context.Context, id int64) (*Payment, error) - Update(ctx context.Context, data *Payment) error - Delete(ctx context.Context, id int64) error + Update(ctx context.Context, data *Payment, tx ...*gorm.DB) error + Delete(ctx context.Context, id int64, tx ...*gorm.DB) error Transaction(ctx context.Context, fn func(db *gorm.DB) error) error } @@ -67,8 +67,11 @@ func (m *defaultPaymentModel) getCacheKeys(data *Payment) []string { return cacheKeys } -func (m *defaultPaymentModel) Insert(ctx context.Context, data *Payment) error { +func (m *defaultPaymentModel) Insert(ctx context.Context, data *Payment, tx ...*gorm.DB) error { err := m.ExecCtx(ctx, func(conn *gorm.DB) error { + if len(tx) > 0 { + conn = tx[0] + } return conn.Create(&data).Error }, m.getCacheKeys(data)...) return err @@ -88,19 +91,21 @@ func (m *defaultPaymentModel) FindOne(ctx context.Context, id int64) (*Payment, } } -func (m *defaultPaymentModel) Update(ctx context.Context, data *Payment) error { +func (m *defaultPaymentModel) Update(ctx context.Context, data *Payment, tx ...*gorm.DB) error { old, err := m.FindOne(ctx, data.Id) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return err } err = m.ExecCtx(ctx, func(conn *gorm.DB) error { - db := conn - return db.Save(data).Error + if len(tx) > 0 { + conn = tx[0] + } + return conn.Save(data).Error }, m.getCacheKeys(old)...) return err } -func (m *defaultPaymentModel) Delete(ctx context.Context, id int64) error { +func (m *defaultPaymentModel) Delete(ctx context.Context, id int64, tx ...*gorm.DB) error { data, err := m.FindOne(ctx, id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -109,8 +114,10 @@ func (m *defaultPaymentModel) Delete(ctx context.Context, id int64) error { return err } err = m.ExecCtx(ctx, func(conn *gorm.DB) error { - db := conn - return db.Delete(&Payment{}, id).Error + if len(tx) > 0 { + conn = tx[0] + } + return conn.Delete(&Payment{}, id).Error }, m.getCacheKeys(data)...) return err } diff --git a/internal/model/payment/payment.go b/internal/model/payment/payment.go index be00208..46ee0a0 100644 --- a/internal/model/payment/payment.go +++ b/internal/model/payment/payment.go @@ -46,13 +46,19 @@ type StripeConfig struct { Payment string `json:"payment"` } -func (l *StripeConfig) Marshal() string { - b, _ := json.Marshal(l) - return string(b) +func (l *StripeConfig) Marshal() ([]byte, error) { + type Alias StripeConfig + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(l), + }) } -func (l *StripeConfig) Unmarshal(s string) error { - return json.Unmarshal([]byte(s), l) +func (l *StripeConfig) Unmarshal(data []byte) error { + type Alias StripeConfig + aux := (*Alias)(l) + return json.Unmarshal(data, &aux) } type AlipayF2FConfig struct { @@ -63,13 +69,19 @@ type AlipayF2FConfig struct { Sandbox bool `json:"sandbox"` } -func (l *AlipayF2FConfig) Marshal() string { - b, _ := json.Marshal(l) - return string(b) +func (l *AlipayF2FConfig) Marshal() ([]byte, error) { + type Alias AlipayF2FConfig + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(l), + }) } -func (l *AlipayF2FConfig) Unmarshal(s string) error { - return json.Unmarshal([]byte(s), l) +func (l *AlipayF2FConfig) Unmarshal(data []byte) error { + type Alias AlipayF2FConfig + aux := (*Alias)(l) + return json.Unmarshal(data, &aux) } type EPayConfig struct { @@ -78,29 +90,38 @@ type EPayConfig struct { Key string `json:"key"` } -func (l *EPayConfig) Marshal() string { - b, _ := json.Marshal(l) - return string(b) +func (l *EPayConfig) Marshal() ([]byte, error) { + type Alias EPayConfig + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(l), + }) } -func (l *EPayConfig) Unmarshal(s string) error { - return json.Unmarshal([]byte(s), l) +func (l *EPayConfig) Unmarshal(data []byte) error { + type Alias EPayConfig + aux := (*Alias)(l) + return json.Unmarshal(data, &aux) } -type PayssionConfig struct { - PmId string `json:"pm_id"` - ApiKey string `json:"api_key"` +type CryptoSaaSConfig struct { + Endpoint string `json:"endpoint"` + AccountID string `json:"account_id"` SecretKey string `json:"secret_key"` - Currency string `json:"currency"` - QueryUrl string `json:"query_url"` - CreateUrl string `json:"create_url"` } -func (l *PayssionConfig) Marshal() string { - b, _ := json.Marshal(l) - return string(b) +func (l *CryptoSaaSConfig) Marshal() ([]byte, error) { + type Alias CryptoSaaSConfig + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(l), + }) } -func (l *PayssionConfig) Unmarshal(s string) error { - return json.Unmarshal([]byte(s), l) +func (l *CryptoSaaSConfig) Unmarshal(data []byte) error { + type Alias CryptoSaaSConfig + aux := (*Alias)(l) + return json.Unmarshal(data, &aux) } diff --git a/internal/model/server/default.go b/internal/model/server/default.go index 4396d85..b013f1a 100644 --- a/internal/model/server/default.go +++ b/internal/model/server/default.go @@ -5,9 +5,7 @@ import ( "errors" "fmt" - "github.com/perfect-panel/ppanel-server/internal/config" - - "github.com/perfect-panel/ppanel-server/pkg/cache" + "github.com/perfect-panel/server/pkg/cache" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) @@ -23,10 +21,10 @@ type ( customServerLogicModel } serverModel interface { - Insert(ctx context.Context, data *Server) error + Insert(ctx context.Context, data *Server, tx ...*gorm.DB) error FindOne(ctx context.Context, id int64) (*Server, error) - Update(ctx context.Context, data *Server) error - Delete(ctx context.Context, id int64) error + Update(ctx context.Context, data *Server, tx ...*gorm.DB) error + Delete(ctx context.Context, id int64, tx ...*gorm.DB) error Transaction(ctx context.Context, fn func(db *gorm.DB) error) error } @@ -62,23 +60,32 @@ func (m *defaultServerModel) batchGetCacheKeys(Servers ...*Server) []string { return keys } + func (m *defaultServerModel) getCacheKeys(data *Server) []string { if data == nil { return []string{} } detailsKey := fmt.Sprintf("%s%v", CacheServerDetailPrefix, data.Id) ServerIdKey := fmt.Sprintf("%s%v", cacheServerIdPrefix, data.Id) - configIdKey := fmt.Sprintf("%s%v", config.ServerConfigCacheKey, data.Id) + //configIdKey := fmt.Sprintf("%s%v", config.ServerConfigCacheKey, data.Id) + //userIDKey := fmt.Sprintf("%s%d", config.ServerUserListCacheKey, data.Id) + + // query protocols to get config keys + cacheKeys := []string{ ServerIdKey, detailsKey, - configIdKey, + //configIdKey, + //userIDKey, } return cacheKeys } -func (m *defaultServerModel) Insert(ctx context.Context, data *Server) error { +func (m *defaultServerModel) Insert(ctx context.Context, data *Server, tx ...*gorm.DB) error { err := m.ExecCtx(ctx, func(conn *gorm.DB) error { + if len(tx) > 0 { + conn = tx[0] + } return conn.Create(&data).Error }, m.getCacheKeys(data)...) return err @@ -98,19 +105,21 @@ func (m *defaultServerModel) FindOne(ctx context.Context, id int64) (*Server, er } } -func (m *defaultServerModel) Update(ctx context.Context, data *Server) error { +func (m *defaultServerModel) Update(ctx context.Context, data *Server, tx ...*gorm.DB) error { old, err := m.FindOne(ctx, data.Id) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return err } err = m.ExecCtx(ctx, func(conn *gorm.DB) error { - db := conn - return db.Save(data).Error + if len(tx) > 0 { + conn = tx[0] + } + return conn.Save(data).Error }, m.getCacheKeys(old)...) return err } -func (m *defaultServerModel) Delete(ctx context.Context, id int64) error { +func (m *defaultServerModel) Delete(ctx context.Context, id int64, tx ...*gorm.DB) error { data, err := m.FindOne(ctx, id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -119,8 +128,10 @@ func (m *defaultServerModel) Delete(ctx context.Context, id int64) error { return err } err = m.ExecCtx(ctx, func(conn *gorm.DB) error { - db := conn - return db.Delete(&Server{}, id).Error + if len(tx) > 0 { + conn = tx[0] + } + return conn.Delete(&Server{}, id).Error }, m.getCacheKeys(data)...) return err } diff --git a/internal/model/server/model.go b/internal/model/server/model.go index 01913fd..58ae7b7 100644 --- a/internal/model/server/model.go +++ b/internal/model/server/model.go @@ -3,8 +3,8 @@ package server import ( "context" "fmt" + "strings" - "github.com/perfect-panel/ppanel-server/internal/config" "gorm.io/gorm" ) @@ -29,6 +29,10 @@ type customServerLogicModel interface { UpdateRuleGroup(ctx context.Context, data *RuleGroup) error DeleteRuleGroup(ctx context.Context, id int64) error QueryAllRuleGroup(ctx context.Context) ([]*RuleGroup, error) + FindServersByTag(ctx context.Context, tag string) ([]*Server, error) + FindServerTags(ctx context.Context) ([]string, error) + + SetDefaultRuleGroup(ctx context.Context, id int64) error } var ( @@ -40,9 +44,10 @@ var ( // ClearCache Clear Cache func (m *customServerModel) ClearCache(ctx context.Context, id int64) error { serverIdKey := fmt.Sprintf("%s%v", cacheServerIdPrefix, id) - configKey := fmt.Sprintf("%s%d", config.ServerConfigCacheKey, id) + //configKey := fmt.Sprintf("%s%d", config.ServerConfigCacheKey, id) + //userListKey := fmt.Sprintf("%s%v", config.ServerUserListCacheKey, id) - return m.DelCacheCtx(ctx, serverIdKey, configKey) + return m.DelCacheCtx(ctx, serverIdKey) } // QueryServerCountByServerGroups Query Server Count By Server Groups @@ -114,13 +119,16 @@ func (m *customServerModel) FindServerDetailByGroupIdsAndIds(ctx context.Context err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error { conn = conn. Model(&Server{}). - Where("enable = ?", true) - if len(groupId) > 0 { - conn = conn.Where("group_id IN ?", groupId) - } - if len(ids) > 0 { - conn = conn.Where("id IN ?", ids) + Where("`enable` = ?", true) + if len(groupId) > 0 && len(ids) > 0 { + // OR is used to connect group_id and id conditions + conn = conn.Where("(`group_id` IN ? OR `id` IN ?)", groupId, ids) + } else if len(groupId) > 0 { + conn = conn.Where("`group_id` IN ?", groupId) + } else if len(ids) > 0 { + conn = conn.Where("`id` IN ?", ids) } + return conn.Order("sort ASC").Find(v).Error }) return list, err @@ -227,10 +235,16 @@ func (m *customServerModel) FindServerListByFilter(ctx context.Context, filter * query = conn.Where("group_id = ?", filter.Group) } if filter.Search != "" { - query = query.Where("name LIKE ? OR server_addr LIKE ?", "%"+filter.Search+"%", "%"+filter.Search+"%") + query = query.Where("name LIKE ? OR server_addr LIKE ? OR tags LIKE ?", "%"+filter.Search+"%", "%"+filter.Search+"%", "%"+filter.Search+"%") } - if filter.Tag != "" { - query = query.Where("tag LIKE ?", "%"+filter.Tag+"%") + if len(filter.Tags) > 0 { + for i, tag := range filter.Tags { + if i == 0 { + query = query.Where("tags LIKE ?", "%"+tag+"%") + } else { + query = query.Or("tags LIKE ?", "%"+tag+"%") + } + } } return query.Count(&total).Limit(filter.Size).Offset((filter.Page - 1) * filter.Size).Find(v).Error }) @@ -239,3 +253,40 @@ func (m *customServerModel) FindServerListByFilter(ctx context.Context, filter * } return total, data, nil } + +func (m *customServerModel) FindServerTags(ctx context.Context) ([]string, error) { + var data []string + err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error { + return conn.Model(&Server{}).Distinct("tags").Pluck("tags", v).Error + }) + var tags []string + for _, tag := range data { + if strings.Contains(tag, ",") { + tags = append(tags, strings.Split(tag, ",")...) + } else { + tags = append(tags, tag) + } + } + return tags, err +} + +func (m *customServerModel) FindServersByTag(ctx context.Context, tag string) ([]*Server, error) { + var data []*Server + err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error { + return conn.Model(&Server{}).Where("FIND_IN_SET(?, tags)", tag).Order("sort ASC").Find(v).Error + }) + return data, err +} + +// SetDefaultRuleGroup sets the default rule group. + +func (m *customServerModel) SetDefaultRuleGroup(ctx context.Context, id int64) error { + return m.ExecCtx(ctx, func(conn *gorm.DB) error { + // Reset all groups to not default + if err := conn.Model(&RuleGroup{}).Where("`id` != ?", id).Update("default", false).Error; err != nil { + return err + } + // Set the specified group as default + return conn.Model(&RuleGroup{}).Where("`id` = ?", id).Update("default", true).Error + }, cacheServerRuleGroupAllKeys, fmt.Sprintf("cache:serverRuleGroup:%v", id)) +} diff --git a/internal/model/server/server.go b/internal/model/server/server.go index 3dbbee5..2da10da 100644 --- a/internal/model/server/server.go +++ b/internal/model/server/server.go @@ -3,26 +3,30 @@ package server import ( "time" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" "gorm.io/gorm" ) const ( - RelayModeNone = "none" - RelayModeAll = "all" - RelayModeRandom = "random" + RelayModeNone = "none" + RelayModeAll = "all" + RelayModeRandom = "random" + RuleGroupTypeReject = "reject" + RuleGroupTypeDefault = "default" + RuleGroupTypeDirect = "direct" ) type ServerFilter struct { Id int64 - Tag string + Tags []string Group int64 Search string Page int Size int } +// Deprecated: use internal/model/node/server.go type Server struct { Id int64 `gorm:"primary_key"` Name string `gorm:"type:varchar(100);not null;default:'';comment:Node Name"` @@ -52,33 +56,32 @@ func (*Server) TableName() string { func (s *Server) BeforeDelete(tx *gorm.DB) error { logger.Debugf("[Server] BeforeDelete") - if err := tx.Exec("UPDATE `server` SET sort = sort - 1 WHERE sort > ?", s.Sort).Error; err != nil { return err } - // 删除后重新排序,防止因 sort 缺口导致问题 - if err := reorderSort(tx); err != nil { - return err - } - return nil } func (s *Server) BeforeUpdate(tx *gorm.DB) error { logger.Debugf("[Server] BeforeUpdate") - var count int64 - if err := tx.Model(&Server{}).Where("sort = ? AND id != ?", s.Sort, s.Id).Count(&count).Error; err != nil { + if err := tx.Set("gorm:query_option", "FOR UPDATE").Model(&Server{}). + Where("sort = ? AND id != ?", s.Sort, s.Id).Count(&count).Error; err != nil { return err } - - if count > 0 { - logger.Debugf("[Server] Duplicate sort found, reordering...") + if count > 1 { + // reorder sort if err := reorderSort(tx); err != nil { + logger.Errorf("[Server] BeforeUpdate reorderSort error: %v", err.Error()) return err } + // get max sort + var maxSort int64 + if err := tx.Model(&Server{}).Select("MAX(sort)").Scan(&maxSort).Error; err != nil { + return err + } + s.Sort = maxSort + 1 } - return nil } @@ -136,6 +139,15 @@ type Hysteria2 struct { } type Tuic struct { + Port int `json:"port"` + DisableSNI bool `json:"disable_sni"` + ReduceRtt bool `json:"reduce_rtt"` + UDPRelayMode string `json:"udp_relay_mode"` + CongestionController string `json:"congestion_controller"` + SecurityConfig SecurityConfig `json:"security_config"` +} + +type AnyTLS struct { Port int `json:"port"` SecurityConfig SecurityConfig `json:"security_config"` } @@ -179,9 +191,11 @@ type RuleGroup struct { Id int64 `gorm:"primary_key"` Icon string `gorm:"type:MEDIUMTEXT;comment:Rule Group Icon"` Name string `gorm:"type:varchar(100);not null;default:'';comment:Rule Group Name"` + Type string `gorm:"type:varchar(100);not null;default:'';comment:Rule Group Type"` Tags string `gorm:"type:text;comment:Selected Node Tags"` Rules string `gorm:"type:MEDIUMTEXT;comment:Rules"` Enable bool `gorm:"type:tinyint(1);not null;default:1;comment:Rule Group Enable"` + Default bool `gorm:"type:tinyint(1);not null;default:0;comment:Rule Group is Default"` CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` UpdatedAt time.Time `gorm:"comment:Update Time"` } @@ -189,19 +203,14 @@ type RuleGroup struct { func (RuleGroup) TableName() string { return "server_rule_group" } - func reorderSort(tx *gorm.DB) error { - var servers []*Server - if err := tx.Model(&Server{}).Order("sort ASC").Find(&servers).Error; err != nil { + var servers []Server + if err := tx.Order("sort, id").Find(&servers).Error; err != nil { return err } - for i, server := range servers { - newSort := int64(i + 1) - if server.Sort != newSort { - if err := tx.Model(&Server{}). - Where("id = ?", server.Id). - Update("sort", newSort).Error; err != nil { + if server.Sort != int64(i)+1 { + if err := tx.Exec("UPDATE `server` SET sort = ? WHERE id = ?", i+1, server.Id).Error; err != nil { return err } } diff --git a/internal/model/subscribe/default.go b/internal/model/subscribe/default.go index 6832026..29e748c 100644 --- a/internal/model/subscribe/default.go +++ b/internal/model/subscribe/default.go @@ -4,8 +4,11 @@ import ( "context" "errors" "fmt" + "strings" - "github.com/perfect-panel/ppanel-server/pkg/cache" + "github.com/perfect-panel/server/internal/model/node" + "github.com/perfect-panel/server/pkg/cache" + "github.com/perfect-panel/server/pkg/tool" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) @@ -57,11 +60,34 @@ func (m *defaultSubscribeModel) getCacheKeys(data *Subscribe) []string { if data == nil { return []string{} } - SubscribeIdKey := fmt.Sprintf("%s%v", cacheSubscribeIdPrefix, data.Id) - cacheKeys := []string{ - SubscribeIdKey, + var keys []string + if data.Nodes != "" { + var nodes []*node.Node + ids := strings.Split(data.Nodes, ",") + + err := m.QueryNoCacheCtx(context.Background(), &nodes, func(conn *gorm.DB, v interface{}) error { + return conn.Model(&node.Node{}).Where("id IN (?)", tool.StringSliceToInt64Slice(ids)).Find(&nodes).Error + }) + if err == nil { + for _, n := range nodes { + keys = append(keys, fmt.Sprintf("%s%d", node.ServerUserListCacheKey, n.ServerId)) + } + } } - return cacheKeys + if data.NodeTags != "" { + var nodes []*node.Node + tags := tool.RemoveDuplicateElements(strings.Split(data.NodeTags, ",")...) + err := m.QueryNoCacheCtx(context.Background(), &nodes, func(conn *gorm.DB, v interface{}) error { + return conn.Model(&node.Node{}).Scopes(InSet("tags", tags)).Find(&nodes).Error + }) + if err == nil { + for _, n := range nodes { + keys = append(keys, fmt.Sprintf("%s%d", node.ServerUserListCacheKey, n.ServerId)) + } + } + } + + return append(keys, fmt.Sprintf("%s%v", cacheSubscribeIdPrefix, data.Id)) } func (m *defaultSubscribeModel) Insert(ctx context.Context, data *Subscribe, tx ...*gorm.DB) error { diff --git a/internal/model/subscribe/model.go b/internal/model/subscribe/model.go index f3b1142..9942046 100644 --- a/internal/model/subscribe/model.go +++ b/internal/model/subscribe/model.go @@ -3,38 +3,37 @@ package subscribe import ( "context" + "github.com/perfect-panel/server/pkg/tool" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) -// type Details struct { -// Id int64 `gorm:"primaryKey"` -// Name string `gorm:"type:varchar(255);not null;default:'';comment:Subscribe Name"` -// Description string `gorm:"type:text;comment:Subscribe Description"` -// UnitPrice int64 `gorm:"type:int;not null;default:0;comment:Unit Price"` -// UnitTime string `gorm:"type:varchar(255);not null;default:'';comment:Unit Time"` -// Discount string `gorm:"type:text;comment:Discount"` -// Replacement int64 `gorm:"type:int;not null;default:0;comment:Replacement"` -// Inventory int64 `gorm:"type:int;not null;default:0;comment:Inventory"` -// Traffic int64 `gorm:"type:int;not null;default:0;comment:Traffic"` -// SpeedLimit int64 `gorm:"type:int;not null;default:0;comment:Speed Limit"` -// DeviceLimit int64 `gorm:"type:int;not null;default:0;comment:Device Limit"` -// GroupId int64 `gorm:"type:bigint;comment:Group Id"` -// Quota int64 `gorm:"type:int;not null;default:0;comment:Quota"` -// Show *bool `gorm:"type:tinyint(1);not null;default:0;comment:Show"` -// Sell *bool `gorm:"type:tinyint(1);not null;default:0;comment:Sell"` -// DeductionRatio int64 `gorm:"type:int;default:0;comment:Deduction Ratio"` -// PurchaseWithDiscount bool `gorm:"type:tinyint(1);default:0;comment:PurchaseWithDiscount"` -// ResetCycle int64 `gorm:"type:int;default:0;comment:Reset Cycle"` -// RenewalReset bool `gorm:"type:tinyint(1);default:0;comment:Renew Reset"` -// } +type FilterParams struct { + Page int // Page Number + Size int // Page Size + Ids []int64 // Subscribe IDs + Node []int64 // Node IDs + Tags []string // Node Tags + Show bool // Show Portal Page + Sell bool // Sell + Language string // Language + DefaultLanguage bool // Default Subscribe Language Data + Search string // Search Keywords +} + +func (p *FilterParams) Normalize() { + if p.Page <= 0 { + p.Page = 1 + } + if p.Size <= 0 { + p.Size = 10 + } +} + type customSubscribeLogicModel interface { - QuerySubscribeListByPage(ctx context.Context, page, size int, group int64, search string) (total int64, list []*Subscribe, err error) - QuerySubscribeList(ctx context.Context) ([]*Subscribe, error) - QuerySubscribeListByShow(ctx context.Context) ([]*Subscribe, error) - QuerySubscribeIdsByServerIdAndServerGroupId(ctx context.Context, serverId, serverGroupId int64) ([]*Subscribe, error) + FilterList(ctx context.Context, params *FilterParams) (int64, []*Subscribe, error) + ClearCache(ctx context.Context, id ...int64) error QuerySubscribeMinSortByIds(ctx context.Context, ids []int64) (int64, error) - QuerySubscribeListByIds(ctx context.Context, ids []int64) ([]*Subscribe, error) } // NewModel returns a model for the database table. @@ -44,54 +43,6 @@ func NewModel(conn *gorm.DB, c *redis.Client) Model { } } -// QuerySubscribeListByPage Get Subscribe List -func (m *customSubscribeModel) QuerySubscribeListByPage(ctx context.Context, page, size int, group int64, search string) (total int64, list []*Subscribe, err error) { - err = m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error { - // About to be abandoned - _ = conn.Model(&Subscribe{}). - Where("sort = ?", 0). - Update("sort", gorm.Expr("id")) - - conn = conn.Model(&Subscribe{}) - if group > 0 { - conn = conn.Where("group_id = ?", group) - } - if search != "" { - conn = conn.Where("`name` like ? or `description` like ?", "%"+search+"%", "%"+search+"%") - } - return conn.Count(&total).Order("sort ASC").Limit(size).Offset((page - 1) * size).Find(v).Error - }) - return total, list, err -} - -// QuerySubscribeList Get Subscribe List -func (m *customSubscribeModel) QuerySubscribeList(ctx context.Context) ([]*Subscribe, error) { - var list []*Subscribe - err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error { - conn = conn.Model(&Subscribe{}) - return conn.Where("`sell` = true").Order("sort ").Find(v).Error - }) - return list, err -} - -func (m *customSubscribeModel) QuerySubscribeIdsByServerIdAndServerGroupId(ctx context.Context, serverId, serverGroupId int64) ([]*Subscribe, error) { - var data []*Subscribe - err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&Subscribe{}).Where("FIND_IN_SET(?, server)", serverId).Or("FIND_IN_SET(?, server_group)", serverGroupId).Find(v).Error - }) - return data, err -} - -// QuerySubscribeListByShow Get Subscribe List By Show -func (m *customSubscribeModel) QuerySubscribeListByShow(ctx context.Context) ([]*Subscribe, error) { - var list []*Subscribe - err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error { - conn = conn.Model(&Subscribe{}) - return conn.Where("`show` = true").Find(v).Error - }) - return list, err -} - func (m *customSubscribeModel) QuerySubscribeMinSortByIds(ctx context.Context, ids []int64) (int64, error) { var minSort int64 err := m.QueryNoCacheCtx(ctx, &minSort, func(conn *gorm.DB, v interface{}) error { @@ -100,10 +51,106 @@ func (m *customSubscribeModel) QuerySubscribeMinSortByIds(ctx context.Context, i return minSort, err } -func (m *customSubscribeModel) QuerySubscribeListByIds(ctx context.Context, ids []int64) ([]*Subscribe, error) { - var list []*Subscribe - err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&Subscribe{}).Where("id IN ?", ids).Find(v).Error - }) - return list, err +func (m *customSubscribeModel) ClearCache(ctx context.Context, ids ...int64) error { + if len(ids) <= 0 { + return nil + } + + var cacheKeys []string + for _, id := range ids { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + cacheKeys = append(cacheKeys, m.getCacheKeys(data)...) + } + return m.CachedConn.DelCacheCtx(ctx, cacheKeys...) +} + +// FilterList Filter Subscribe List +func (m *customSubscribeModel) FilterList(ctx context.Context, params *FilterParams) (int64, []*Subscribe, error) { + if params == nil { + params = &FilterParams{} + } + params.Normalize() + + var list []*Subscribe + var total int64 + + // 构建查询函数 + buildQuery := func(conn *gorm.DB, lang string) *gorm.DB { + query := conn.Model(&Subscribe{}) + + if params.Search != "" { + s := "%" + params.Search + "%" + query = query.Where("`name` LIKE ? OR `description` LIKE ?", s, s) + } + if params.Show { + query = query.Where("`show` = true") + } + if params.Sell { + query = query.Where("`sell` = true") + } + + if len(params.Ids) > 0 { + query = query.Where("id IN ?", params.Ids) + } + if len(params.Node) > 0 { + query = query.Scopes(InSet("nodes", tool.Int64SliceToStringSlice(params.Node))) + } + + if len(params.Tags) > 0 { + query = query.Scopes(InSet("node_tags", params.Tags)) + } + if lang != "" { + query = query.Where("language = ?", lang) + } else if params.DefaultLanguage { + query = query.Where("language = ''") + } + + return query + } + + // 查询数据 + queryFunc := func(lang string) error { + return m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error { + query := buildQuery(conn, lang) + if err := query.Count(&total).Error; err != nil { + return err + } + return query.Order("sort ASC"). + Limit(params.Size). + Offset((params.Page - 1) * params.Size). + Find(v).Error + }) + } + + err := queryFunc(params.Language) + if err != nil { + return 0, nil, err + } + + // fallback 默认语言 + if params.DefaultLanguage && total == 0 { + err = queryFunc("") + if err != nil { + return 0, nil, err + } + } + + return total, list, nil +} + +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 + } + + query := db.Where("1=0") + for _, v := range values { + query = query.Or("FIND_IN_SET(?, "+field+")", v) + } + return query + } } diff --git a/internal/model/subscribe/subscribe.go b/internal/model/subscribe/subscribe.go index 2c705f8..a80ea63 100644 --- a/internal/model/subscribe/subscribe.go +++ b/internal/model/subscribe/subscribe.go @@ -9,6 +9,7 @@ import ( type Subscribe struct { Id int64 `gorm:"primaryKey"` Name string `gorm:"type:varchar(255);not null;default:'';comment:Subscribe Name"` + Language string `gorm:"type:varchar(255);not null;default:'';comment:Language"` Description string `gorm:"type:text;comment:Subscribe Description"` UnitPrice int64 `gorm:"type:int;not null;default:0;comment:Unit Price"` UnitTime string `gorm:"type:varchar(255);not null;default:'';comment:Unit Time"` @@ -19,9 +20,8 @@ type Subscribe struct { SpeedLimit int64 `gorm:"type:int;not null;default:0;comment:Speed Limit"` DeviceLimit int64 `gorm:"type:int;not null;default:0;comment:Device Limit"` Quota int64 `gorm:"type:int;not null;default:0;comment:Quota"` - GroupId int64 `gorm:"type:bigint;comment:Group Id"` - ServerGroup string `gorm:"type:varchar(255);comment:Server Group"` - Server string `gorm:"type:varchar(255);comment:Server"` + Nodes string `gorm:"type:varchar(255);comment:Node Ids"` + NodeTags string `gorm:"type:varchar(255);comment:Node Tags"` 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"` Sort int64 `gorm:"type:int;not null;default:0;comment:Sort"` @@ -48,6 +48,28 @@ func (s *Subscribe) BeforeCreate(tx *gorm.DB) error { return nil } +func (s *Subscribe) BeforeDelete(tx *gorm.DB) error { + if err := tx.Exec("UPDATE `subscribe` SET sort = sort - 1 WHERE sort > ?", s.Sort).Error; err != nil { + return err + } + return nil +} +func (s *Subscribe) BeforeUpdate(tx *gorm.DB) error { + var count int64 + if err := tx.Set("gorm:query_option", "FOR UPDATE").Model(&Subscribe{}). + Where("sort = ? AND id != ?", s.Sort, s.Id).Count(&count).Error; err != nil { + return err + } + if count > 0 { + var maxSort int64 + if err := tx.Model(&Subscribe{}).Select("MAX(sort)").Scan(&maxSort).Error; err != nil { + return err + } + s.Sort = maxSort + 1 + } + return nil +} + type Discount struct { Months int64 `json:"months"` Discount int64 `json:"discount"` diff --git a/internal/model/subscribeType/default.go b/internal/model/subscribeType/default.go deleted file mode 100644 index 788f8f2..0000000 --- a/internal/model/subscribeType/default.go +++ /dev/null @@ -1,117 +0,0 @@ -package subscribeType - -import ( - "context" - "errors" - "fmt" - - "github.com/perfect-panel/ppanel-server/pkg/cache" - "github.com/redis/go-redis/v9" - "gorm.io/gorm" -) - -var _ Model = (*customSubscribeTypeModel)(nil) -var ( - cacheSubscribeTypeIdPrefix = "cache:subscribeType:id:" -) - -type ( - Model interface { - subscribeTypeModel - customSubscribeTypeLogicModel - } - subscribeTypeModel interface { - Insert(ctx context.Context, data *SubscribeType) error - FindOne(ctx context.Context, id int64) (*SubscribeType, error) - Update(ctx context.Context, data *SubscribeType) error - Delete(ctx context.Context, id int64) error - Transaction(ctx context.Context, fn func(db *gorm.DB) error) error - } - - customSubscribeTypeModel struct { - *defaultSubscribeTypeModel - } - defaultSubscribeTypeModel struct { - cache.CachedConn - table string - } -) - -func newSubscribeTypeModel(db *gorm.DB, c *redis.Client) *defaultSubscribeTypeModel { - return &defaultSubscribeTypeModel{ - CachedConn: cache.NewConn(db, c), - table: "`SubscribeType`", - } -} - -//nolint:unused -func (m *defaultSubscribeTypeModel) batchGetCacheKeys(SubscribeTypes ...*SubscribeType) []string { - var keys []string - for _, subscribeType := range SubscribeTypes { - keys = append(keys, m.getCacheKeys(subscribeType)...) - } - return keys - -} -func (m *defaultSubscribeTypeModel) getCacheKeys(data *SubscribeType) []string { - if data == nil { - return []string{} - } - SubscribeTypeIdKey := fmt.Sprintf("%s%v", cacheSubscribeTypeIdPrefix, data.Id) - cacheKeys := []string{ - SubscribeTypeIdKey, - } - return cacheKeys -} - -func (m *defaultSubscribeTypeModel) Insert(ctx context.Context, data *SubscribeType) error { - err := m.ExecCtx(ctx, func(conn *gorm.DB) error { - return conn.Create(&data).Error - }, m.getCacheKeys(data)...) - return err -} - -func (m *defaultSubscribeTypeModel) FindOne(ctx context.Context, id int64) (*SubscribeType, error) { - SubscribeTypeIdKey := fmt.Sprintf("%s%v", cacheSubscribeTypeIdPrefix, id) - var resp SubscribeType - err := m.QueryCtx(ctx, &resp, SubscribeTypeIdKey, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&SubscribeType{}).Where("`id` = ?", id).First(&resp).Error - }) - switch { - case err == nil: - return &resp, nil - default: - return nil, err - } -} - -func (m *defaultSubscribeTypeModel) Update(ctx context.Context, data *SubscribeType) error { - old, err := m.FindOne(ctx, data.Id) - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return err - } - err = m.ExecCtx(ctx, func(conn *gorm.DB) error { - db := conn - return db.Save(data).Error - }, m.getCacheKeys(old)...) - return err -} - -func (m *defaultSubscribeTypeModel) Delete(ctx context.Context, id int64) error { - data, err := m.FindOne(ctx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil - } - return err - } - err = m.ExecCtx(ctx, func(conn *gorm.DB) error { - db := conn - return db.Delete(&SubscribeType{}, id).Error - }, m.getCacheKeys(data)...) - return err -} - -func (m *defaultSubscribeTypeModel) Transaction(ctx context.Context, fn func(db *gorm.DB) error) error { - return m.TransactCtx(ctx, fn) -} diff --git a/internal/model/subscribeType/model.go b/internal/model/subscribeType/model.go deleted file mode 100644 index 52e7e0f..0000000 --- a/internal/model/subscribeType/model.go +++ /dev/null @@ -1,16 +0,0 @@ -package subscribeType - -import ( - "github.com/redis/go-redis/v9" - "gorm.io/gorm" -) - -type customSubscribeTypeLogicModel interface { -} - -// NewModel returns a model for the database table. -func NewModel(conn *gorm.DB, c *redis.Client) Model { - return &customSubscribeTypeModel{ - defaultSubscribeTypeModel: newSubscribeTypeModel(conn, c), - } -} diff --git a/internal/model/subscribeType/subscribeType.go b/internal/model/subscribeType/subscribeType.go deleted file mode 100644 index c0a9d94..0000000 --- a/internal/model/subscribeType/subscribeType.go +++ /dev/null @@ -1,15 +0,0 @@ -package subscribeType - -import "time" - -type SubscribeType struct { - Id int64 `gorm:"primary_key"` - Name string `gorm:"type:varchar(50);default:'';not null;comment:订阅类型"` - Mark string `gorm:"type:varchar(255);default:'';not null;comment:订阅标识"` - CreatedAt time.Time `gorm:"<-:create;comment:创建时间"` - UpdatedAt time.Time `gorm:"comment:更新时间"` -} - -func (SubscribeType) TableName() string { - return "subscribe_type" -} diff --git a/internal/model/system/default.go b/internal/model/system/default.go index 09f53fc..e34013f 100644 --- a/internal/model/system/default.go +++ b/internal/model/system/default.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/perfect-panel/ppanel-server/pkg/cache" + "github.com/perfect-panel/server/pkg/cache" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) diff --git a/internal/model/system/model.go b/internal/model/system/model.go index 1439431..a7c28e1 100644 --- a/internal/model/system/model.go +++ b/internal/model/system/model.go @@ -3,7 +3,7 @@ package system import ( "context" - "github.com/perfect-panel/ppanel-server/internal/config" + "github.com/perfect-panel/server/internal/config" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) @@ -19,6 +19,7 @@ type customSystemLogicModel interface { GetTosConfig(ctx context.Context) ([]*System, error) GetCurrencyConfig(ctx context.Context) ([]*System, error) GetVerifyCodeConfig(ctx context.Context) ([]*System, error) + GetLogConfig(ctx context.Context) ([]*System, error) UpdateNodeMultiplierConfig(ctx context.Context, config string) error FindNodeMultiplierConfig(ctx context.Context) (*System, error) } @@ -152,3 +153,12 @@ func (m *customSystemModel) GetVerifyCodeConfig(ctx context.Context) ([]*System, }) return configs, err } + +// GetLogConfig returns the log config. +func (m *customSystemModel) GetLogConfig(ctx context.Context) ([]*System, error) { + var configs []*System + err := m.QueryNoCacheCtx(ctx, &configs, func(conn *gorm.DB, v interface{}) error { + return conn.Where("`category` = ?", "log").Find(v).Error + }) + return configs, err +} diff --git a/internal/model/task/task.go b/internal/model/task/task.go new file mode 100644 index 0000000..5c1b987 --- /dev/null +++ b/internal/model/task/task.go @@ -0,0 +1,151 @@ +package task + +import ( + "encoding/json" + "time" +) + +type Type int8 + +const ( + Undefined Type = -1 + TypeEmail = iota + TypeQuota +) + +type Task struct { + Id int64 `gorm:"primaryKey;autoIncrement;comment:ID"` + Type int8 `gorm:"not null;comment:Task Type"` + Scope string `gorm:"type:text;comment:Task Scope"` + Content string `gorm:"type:text;comment:Task Content"` + Status int8 `gorm:"not null;default:0;comment:Task Status: 0: Pending, 1: In Progress, 2: Completed, 3: Failed"` + Errors string `gorm:"type:text;comment:Task Errors"` + Total uint64 `gorm:"column:total;not null;default:0;comment:Total Number"` + Current uint64 `gorm:"column:current;not null;default:0;comment:Current Number"` + CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` + UpdatedAt time.Time `gorm:"comment:Update Time"` +} + +func (Task) TableName() string { + return "task" +} + +type ScopeType int8 + +const ( + ScopeAll ScopeType = iota + 1 // All users + ScopeActive // Active users + ScopeExpired // Expired users + ScopeNone // No Subscribe + ScopeSkip // Skip user filtering +) + +func (t ScopeType) Int8() int8 { + return int8(t) +} + +type EmailScope struct { + Type int8 `gorm:"not null;comment:Scope Type"` + RegisterStartTime int64 `json:"register_start_time"` + RegisterEndTime int64 `json:"register_end_time"` + Recipients []string `json:"recipients"` // list of email addresses + Additional []string `json:"additional"` // additional email addresses + Scheduled int64 `json:"scheduled"` // scheduled time (unix timestamp) + Interval uint8 `json:"interval"` // interval in seconds + Limit uint64 `json:"limit"` // daily send limit +} + +func (s *EmailScope) Marshal() ([]byte, error) { + type Alias EmailScope + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(s), + }) +} + +func (s *EmailScope) Unmarshal(data []byte) error { + type Alias EmailScope + aux := (*Alias)(s) + return json.Unmarshal(data, &aux) +} + +type EmailContent struct { + Subject string `json:"subject"` + Content string `json:"content"` +} + +func (c *EmailContent) Marshal() ([]byte, error) { + type Alias EmailContent + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(c), + }) +} + +func (c *EmailContent) Unmarshal(data []byte) error { + type Alias EmailContent + aux := (*Alias)(c) + return json.Unmarshal(data, &aux) +} + +type QuotaScope struct { + Subscribers []int64 `json:"subscribers"` // Subscribe IDs + IsActive *bool `json:"is_active"` // filter by active status + StartTime int64 `json:"start_time"` // filter by subscription start time + EndTime int64 `json:"end_time"` // filter by subscription end time + Objects []int64 `json:"recipients"` // list of user subs IDs +} + +func (s *QuotaScope) Marshal() ([]byte, error) { + type Alias QuotaScope + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(s), + }) +} + +func (s *QuotaScope) Unmarshal(data []byte) error { + type Alias QuotaScope + aux := (*Alias)(s) + return json.Unmarshal(data, &aux) +} + +type QuotaContent struct { + ResetTraffic bool `json:"reset_traffic"` // whether to reset traffic + Days uint64 `json:"days,omitempty"` // days to add + GiftType uint8 `json:"gift_type,omitempty"` // 1: Fixed, 2: Ratio + GiftValue uint64 `json:"gift_value,omitempty"` // value of the gift type +} + +func (c *QuotaContent) Marshal() ([]byte, error) { + type Alias QuotaContent + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(c), + }) +} + +func (c *QuotaContent) Unmarshal(data []byte) error { + type Alias QuotaContent + aux := (*Alias)(c) + return json.Unmarshal(data, &aux) +} + +func ParseScopeType(t int8) ScopeType { + switch t { + case 1: + return ScopeAll + case 2: + return ScopeActive + case 3: + return ScopeExpired + case 4: + return ScopeNone + default: + return ScopeSkip + } +} diff --git a/internal/model/ticket/default.go b/internal/model/ticket/default.go index d52a6df..10db8fe 100644 --- a/internal/model/ticket/default.go +++ b/internal/model/ticket/default.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/perfect-panel/ppanel-server/pkg/cache" + "github.com/perfect-panel/server/pkg/cache" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) diff --git a/internal/model/user/authMethod.go b/internal/model/user/authMethod.go index 07faabd..18ce951 100644 --- a/internal/model/user/authMethod.go +++ b/internal/model/user/authMethod.go @@ -3,6 +3,7 @@ package user import ( "context" + "github.com/perfect-panel/server/pkg/logger" "gorm.io/gorm" ) @@ -31,24 +32,50 @@ func (m *defaultUserModel) FindUserAuthMethodByPlatform(ctx context.Context, use } func (m *defaultUserModel) InsertUserAuthMethods(ctx context.Context, data *AuthMethods, tx ...*gorm.DB) error { + u, err := m.FindOne(ctx, data.UserId) + if err != nil { + return err + } + return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error { if len(tx) > 0 { conn = tx[0] } - return conn.Model(&AuthMethods{}).Create(data).Error + if err = conn.Model(&AuthMethods{}).Create(data).Error; err != nil { + return err + } + return m.ClearUserCache(ctx, u) }) } func (m *defaultUserModel) UpdateUserAuthMethods(ctx context.Context, data *AuthMethods, tx ...*gorm.DB) error { + u, err := m.FindOne(ctx, data.UserId) + if err != nil { + return err + } + return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error { if len(tx) > 0 { conn = tx[0] } - return conn.Model(&AuthMethods{}).Where("user_id = ? AND auth_type = ?", data.UserId, data.AuthType).Save(data).Error + err = conn.Model(&AuthMethods{}).Where("user_id = ? AND auth_type = ?", data.UserId, data.AuthType).Save(data).Error + if err != nil { + return err + } + return m.ClearUserCache(ctx, u) }) } func (m *defaultUserModel) DeleteUserAuthMethods(ctx context.Context, userId int64, platform string, tx ...*gorm.DB) error { + u, err := m.FindOne(ctx, userId) + if err != nil { + return err + } + defer func() { + if err = m.ClearUserCache(context.Background(), u); err != nil { + logger.Errorf("[UserModel] clear user cache failed: %v", err.Error()) + } + }() return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error { if len(tx) > 0 { conn = tx[0] diff --git a/internal/model/user/cache.go b/internal/model/user/cache.go new file mode 100644 index 0000000..a39f748 --- /dev/null +++ b/internal/model/user/cache.go @@ -0,0 +1,285 @@ +package user + +import ( + "context" + "fmt" + + "github.com/perfect-panel/server/pkg/logger" +) + +type CacheKeyGenerator interface { + GetCacheKeys() []string +} + +type CacheManager interface { + ClearCache(ctx context.Context, keys ...string) error + ClearModelCache(ctx context.Context, models ...CacheKeyGenerator) error +} + +type UserCacheManager struct { + model *defaultUserModel +} + +func NewUserCacheManager(model *defaultUserModel) *UserCacheManager { + return &UserCacheManager{ + model: model, + } +} + +func (c *UserCacheManager) ClearCache(ctx context.Context, keys ...string) error { + if len(keys) == 0 { + return nil + } + return c.model.CachedConn.DelCacheCtx(ctx, keys...) +} + +func (c *UserCacheManager) ClearModelCache(ctx context.Context, models ...CacheKeyGenerator) error { + var allKeys []string + for _, model := range models { + if model != nil { + allKeys = append(allKeys, model.GetCacheKeys()...) + } + } + return c.ClearCache(ctx, allKeys...) +} + +func (u *User) GetCacheKeys() []string { + if u == nil { + return []string{} + } + keys := []string{ + fmt.Sprintf("%s%d", cacheUserIdPrefix, u.Id), + } + + for _, auth := range u.AuthMethods { + if auth.AuthType == "email" { + keys = append(keys, fmt.Sprintf("%s%s", cacheUserEmailPrefix, auth.AuthIdentifier)) + break + } + } + return keys +} + +func (s *Subscribe) GetCacheKeys() []string { + if s == nil { + return []string{} + } + keys := make([]string, 0) + + if s.Token != "" { + keys = append(keys, fmt.Sprintf("%s%s", cacheUserSubscribeTokenPrefix, s.Token)) + } + if s.UserId != 0 { + keys = append(keys, fmt.Sprintf("%s%d", cacheUserSubscribeUserPrefix, s.UserId)) + } + if s.Id != 0 { + keys = append(keys, fmt.Sprintf("%s%d", cacheUserSubscribeIdPrefix, s.Id)) + } + return keys +} + +func (s *Subscribe) GetExtendedCacheKeys(model *defaultUserModel) []string { + keys := s.GetCacheKeys() + + if s.SubscribeId != 0 && model != nil { + serverKeys := model.getServerRelatedCacheKeys(s.SubscribeId) + keys = append(keys, serverKeys...) + } + + return keys +} + +func (d *Device) GetCacheKeys() []string { + if d == nil { + return []string{} + } + keys := []string{} + + if d.Id != 0 { + keys = append(keys, fmt.Sprintf("%s%d", cacheUserDeviceIdPrefix, d.Id)) + } + if d.Identifier != "" { + keys = append(keys, fmt.Sprintf("%s%s", cacheUserDeviceNumberPrefix, d.Identifier)) + } + return keys +} + +func (a *AuthMethods) GetCacheKeys() []string { + if a == nil { + return []string{} + } + keys := []string{} + + if a.UserId != 0 { + keys = append(keys, fmt.Sprintf("%s%d", cacheUserIdPrefix, a.UserId)) + } + if a.AuthType == "email" && a.AuthIdentifier != "" { + keys = append(keys, fmt.Sprintf("%s%s", cacheUserEmailPrefix, a.AuthIdentifier)) + } + return keys +} + +func (m *defaultUserModel) GetCacheManager() *UserCacheManager { + return NewUserCacheManager(m) +} + +func (m *defaultUserModel) getServerRelatedCacheKeys(subscribeId int64) []string { + // 这里复用了 model.go 中的逻辑,但简化了实现 + keys := []string{} + + if subscribeId == 0 { + return keys + } + + // 这里需要从 getSubscribeCacheKey 方法中提取服务器相关的逻辑 + // 为了避免重复查询,我们可以在需要时才获取 + // 或者可以将这个逻辑移到一个统一的地方 + + return keys +} + +func (m *defaultUserModel) ClearUserCache(ctx context.Context, users ...*User) error { + cacheManager := m.GetCacheManager() + models := make([]CacheKeyGenerator, len(users)) + for i, user := range users { + models[i] = user + } + return cacheManager.ClearModelCache(ctx, models...) +} + +func (m *defaultUserModel) ClearSubscribeCacheByModels(ctx context.Context, subscribes ...*Subscribe) error { + cacheManager := m.GetCacheManager() + models := make([]CacheKeyGenerator, len(subscribes)) + for i, subscribe := range subscribes { + models[i] = subscribe + } + return cacheManager.ClearModelCache(ctx, models...) +} + +func (m *defaultUserModel) ClearDeviceCache(ctx context.Context, devices ...*Device) error { + cacheManager := m.GetCacheManager() + models := make([]CacheKeyGenerator, len(devices)) + for i, device := range devices { + models[i] = device + } + return cacheManager.ClearModelCache(ctx, models...) +} + +func (m *defaultUserModel) ClearAuthMethodCache(ctx context.Context, authMethods ...*AuthMethods) error { + cacheManager := m.GetCacheManager() + models := make([]CacheKeyGenerator, len(authMethods)) + for i, auth := range authMethods { + models[i] = auth + } + return cacheManager.ClearModelCache(ctx, models...) +} + +func (m *defaultUserModel) BatchClearRelatedCache(ctx context.Context, user *User) error { + if user == nil { + return nil + } + + cacheManager := m.GetCacheManager() + + var allModels []CacheKeyGenerator + allModels = append(allModels, user) + + for _, auth := range user.AuthMethods { + allModels = append(allModels, &auth) + } + + for _, device := range user.UserDevices { + allModels = append(allModels, &device) + } + + subscribes, err := m.QueryUserSubscribe(ctx, user.Id) + if err != nil { + logger.Errorf("failed to query user subscribes for cache clearing: %v", err) + } else { + for _, sub := range subscribes { + subModel := &Subscribe{ + Id: sub.Id, + UserId: sub.UserId, + Token: sub.Token, + SubscribeId: sub.SubscribeId, + } + allModels = append(allModels, subModel) + } + } + + return cacheManager.ClearModelCache(ctx, allModels...) +} + +func (m *defaultUserModel) CacheInvalidationHandler(ctx context.Context, operation string, modelType string, model interface{}) error { + switch operation { + case "create", "update", "delete": + switch modelType { + case "user": + if user, ok := model.(*User); ok { + return m.BatchClearRelatedCache(ctx, user) + } + case "subscribe": + if subscribe, ok := model.(*Subscribe); ok { + return m.ClearSubscribeCacheByModels(ctx, subscribe) + } + case "device": + if device, ok := model.(*Device); ok { + return m.ClearDeviceCache(ctx, device) + } + case "authmethod": + if authMethod, ok := model.(*AuthMethods); ok { + return m.ClearAuthMethodCache(ctx, authMethod) + } + } + } + return nil +} + +func (m *customUserModel) GetRelatedCacheKeys(ctx context.Context, modelType string, modelId int64) ([]string, error) { + var keys []string + + switch modelType { + case "user": + user, err := m.FindOne(ctx, modelId) + if err != nil { + return nil, err + } + keys = append(keys, user.GetCacheKeys()...) + + auths, err := m.FindUserAuthMethods(ctx, modelId) + if err == nil { + for _, auth := range auths { + keys = append(keys, auth.GetCacheKeys()...) + } + } + + subscribes, err := m.QueryUserSubscribe(ctx, modelId) + if err == nil { + for _, sub := range subscribes { + subModel := &Subscribe{ + Id: sub.Id, + UserId: sub.UserId, + Token: sub.Token, + SubscribeId: sub.SubscribeId, + } + keys = append(keys, subModel.GetCacheKeys()...) + } + } + + case "subscribe": + subscribe, err := m.FindOneSubscribe(ctx, modelId) + if err != nil { + return nil, err + } + keys = append(keys, subscribe.GetCacheKeys()...) + + case "device": + device, err := m.FindOneDevice(ctx, modelId) + if err != nil { + return nil, err + } + keys = append(keys, device.GetCacheKeys()...) + } + + return keys, nil +} diff --git a/internal/model/user/default.go b/internal/model/user/default.go index 07ebc16..e2a326a 100644 --- a/internal/model/user/default.go +++ b/internal/model/user/default.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/perfect-panel/ppanel-server/pkg/cache" + "github.com/perfect-panel/server/pkg/cache" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) @@ -48,29 +48,20 @@ func newUserModel(db *gorm.DB, c *redis.Client) *defaultUserModel { func (m *defaultUserModel) batchGetCacheKeys(users ...*User) []string { var keys []string for _, user := range users { - keys = append(keys, m.getCacheKeys(user)...) + keys = append(keys, user.GetCacheKeys()...) } return keys - } + func (m *defaultUserModel) getCacheKeys(data *User) []string { if data == nil { return []string{} } - userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id) - cacheKeys := []string{ - userIdKey, - } - // email key - if len(data.AuthMethods) > 0 { - for _, auth := range data.AuthMethods { - if auth.AuthType == "email" { - cacheKeys = append(cacheKeys, fmt.Sprintf("%s%v", cacheUserEmailPrefix, auth.AuthIdentifier)) - break - } - } - } - return cacheKeys + return data.GetCacheKeys() +} + +func (m *defaultUserModel) clearUserCache(ctx context.Context, data ...*User) error { + return m.ClearUserCache(ctx, data...) } func (m *defaultUserModel) FindOneByEmail(ctx context.Context, email string) (*User, error) { @@ -127,53 +118,38 @@ func (m *defaultUserModel) Delete(ctx context.Context, id int64, tx ...*gorm.DB) } return err } - err = m.ExecCtx(ctx, func(conn *gorm.DB) error { - if len(tx) > 0 { - conn = tx[0] + + // 使用批量相关缓存清理,包含所有相关数据的缓存 + defer func() { + if clearErr := m.BatchClearRelatedCache(ctx, data); clearErr != nil { + // 记录清理缓存错误,但不阻断删除操作 } - return conn.Transaction(func(db *gorm.DB) error { - if err := db.Model(&User{}).Where("`id` = ?", id).Delete(&User{}).Error; err != nil { - return err - } - if err := db.Model(&AuthMethods{}).Where("`user_id` = ?", id).Delete(&User{}).Error; err != nil { - return err - } - if err := db.Model(&Subscribe{}).Where("`user_id` = ?", id).Delete(&User{}).Error; err != nil { - return err - } - if err := db.Model(&BalanceLog{}).Where("`user_id` = ?", id).Delete(&User{}).Error; err != nil { - return err - } - if err := db.Model(&GiftAmountLog{}).Where("`user_id` = ?", id).Delete(&User{}).Error; err != nil { - return err - } - if err := db.Model(&LoginLog{}).Where("`user_id` = ?", id).Delete(&User{}).Error; err != nil { - return err - } - if err := db.Model(&SubscribeLog{}).Where("`user_id` = ?", id).Delete(&User{}).Error; err != nil { - return err - } - if err := db.Model(&Device{}).Where("`user_id` = ?", id).Delete(&User{}).Error; err != nil { - return err - } + }() - subs, err := m.QueryUserSubscribe(ctx, id) - if err != nil { - return err - } - for _, sub := range subs { - if err := m.DeleteSubscribeById(ctx, sub.Id, db); err != nil { - return err - } - } + return m.TransactCtx(ctx, func(db *gorm.DB) error { + if len(tx) > 0 { + db = tx[0] + } - if err := db.Model(&CommissionLog{}).Where("`user_id` = ?", id).Delete(&User{}).Error; err != nil { - return err - } - return nil - }) - }, m.getCacheKeys(data)...) - return err + // 删除用户相关的所有数据 + if err := db.Model(&User{}).Where("`id` = ?", id).Delete(&User{}).Error; err != nil { + return err + } + + if err := db.Model(&AuthMethods{}).Where("`user_id` = ?", id).Delete(&AuthMethods{}).Error; err != nil { + return err + } + + if err := db.Model(&Subscribe{}).Where("`user_id` = ?", id).Delete(&Subscribe{}).Error; err != nil { + return err + } + + if err := db.Model(&Device{}).Where("`user_id` = ?", id).Delete(&Device{}).Error; err != nil { + return err + } + + return nil + }) } func (m *defaultUserModel) Transaction(ctx context.Context, fn func(db *gorm.DB) error) error { diff --git a/internal/model/user/device.go b/internal/model/user/device.go index 7258819..f823991 100644 --- a/internal/model/user/device.go +++ b/internal/model/user/device.go @@ -46,18 +46,27 @@ func (m *customUserModel) QueryDevicePageList(ctx context.Context, userId, subsc 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` = ? and `subscribe_id` = ?", userId).Count(&total).Find(&list).Error + }) + return list, total, err +} + func (m *customUserModel) UpdateDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error { old, err := m.FindOneDevice(ctx, data.Id) if err != nil { return err } - deviceIdKey := fmt.Sprintf("%s%v", cacheUserDeviceIdPrefix, old.Id) err = m.ExecCtx(ctx, func(conn *gorm.DB) error { if len(tx) > 0 { conn = tx[0] } return conn.Save(data).Error - }, deviceIdKey) + }, old.GetCacheKeys()...) return err } @@ -69,12 +78,26 @@ func (m *customUserModel) DeleteDevice(ctx context.Context, id int64, tx ...*gor } return err } - deviceIdKey := fmt.Sprintf("%s%v", cacheUserDeviceIdPrefix, data.Id) err = m.ExecCtx(ctx, func(conn *gorm.DB) error { if len(tx) > 0 { conn = tx[0] } return conn.Delete(&Device{}, id).Error - }, deviceIdKey) + }, data.GetCacheKeys()...) 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 + }) +} diff --git a/internal/model/user/log.go b/internal/model/user/log.go deleted file mode 100644 index d3e1105..0000000 --- a/internal/model/user/log.go +++ /dev/null @@ -1,81 +0,0 @@ -package user - -import ( - "context" - - "github.com/pkg/errors" - "gorm.io/gorm" -) - -func (m *customUserModel) InsertSubscribeLog(ctx context.Context, log *SubscribeLog) error { - return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error { - return conn.Create(log).Error - }) -} - -func (m *customUserModel) FilterSubscribeLogList(ctx context.Context, page, size int, filter *SubscribeLogFilterParams) ([]*SubscribeLog, int64, error) { - var list []*SubscribeLog - var total int64 - err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error { - query := conn.Model(&SubscribeLog{}) - if filter != nil { - if filter.UserId != 0 { - query = query.Where("user_id = ?", filter.UserId) - } - if filter.UserSubscribeId != 0 { - query = query.Where("user_subscribe_id = ?", filter.UserSubscribeId) - } - if filter.IP != "" { - query = query.Where("ip LIKE ?", "%"+filter.IP+"%") - } - if filter.Token != "" { - query = query.Where("token LIKE ?", "%"+filter.Token+"%") - } - if filter.UserAgent != "" { - query = query.Where("user_agent LIKE ?", "%"+filter.UserAgent+"%") - } - } - return query.Count(&total).Limit(size).Offset((page - 1) * size).Find(v).Error - }) - - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return nil, 0, err - } - - return list, total, nil -} - -func (m *customUserModel) InsertLoginLog(ctx context.Context, log *LoginLog) error { - return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error { - return conn.Create(log).Error - }) -} - -func (m *customUserModel) FilterLoginLogList(ctx context.Context, page, size int, filter *LoginLogFilterParams) ([]*LoginLog, int64, error) { - var list []*LoginLog - var total int64 - err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error { - query := conn.Model(&LoginLog{}) - if filter != nil { - if filter.UserId != 0 { - query = query.Where("user_id = ?", filter.UserId) - } - if filter.IP != "" { - query = query.Where("ip LIKE ?", "%"+filter.IP+"%") - } - if filter.UserAgent != "" { - query = query.Where("user_agent LIKE ?", "%"+filter.UserAgent+"%") - } - if filter.Success != nil { - query = query.Where("success = ?", *filter.Success) - } - } - return query.Count(&total).Limit(size).Offset((page - 1) * size).Find(v).Error - }) - - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return nil, 0, err - } - - return list, total, nil -} diff --git a/internal/model/user/model.go b/internal/model/user/model.go index 3175331..ffc5020 100644 --- a/internal/model/user/model.go +++ b/internal/model/user/model.go @@ -2,15 +2,12 @@ package user import ( "context" - "errors" "fmt" "time" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/server" - "github.com/perfect-panel/ppanel-server/internal/model/subscribe" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/internal/model/order" + "github.com/perfect-panel/server/internal/model/subscribe" + "github.com/redis/go-redis/v9" "gorm.io/gorm" ) @@ -32,6 +29,7 @@ type SubscribeDetails struct { Subscribe *subscribe.Subscribe `gorm:"foreignKey:SubscribeId;references:Id"` StartTime time.Time `gorm:"default:CURRENT_TIMESTAMP(3);not null;comment:Subscription Start Time"` ExpireTime time.Time `gorm:"default:NULL;comment:Subscription Expire Time"` + FinishedAt *time.Time `gorm:"default:NULL;comment:Finished Time"` Traffic int64 `gorm:"default:0;comment:Traffic"` Download int64 `gorm:"default:0;comment:Download Traffic"` Upload int64 `gorm:"default:0;comment:Upload Traffic"` @@ -62,6 +60,7 @@ type UserFilterParams struct { UserId *int64 SubscribeId *int64 UserSubscribeId *int64 + Order string // Order by id, e.g., "desc" } type customUserLogicModel interface { @@ -78,7 +77,6 @@ type customUserLogicModel interface { QueryUserSubscribe(ctx context.Context, userId int64, status ...int64) ([]*SubscribeDetails, error) FindOneSubscribeDetailsById(ctx context.Context, id int64) (*SubscribeDetails, error) FindOneUserSubscribe(ctx context.Context, id int64) (*SubscribeDetails, error) - InsertBalanceLog(ctx context.Context, data *BalanceLog, tx ...*gorm.DB) error FindUsersSubscribeBySubscribeId(ctx context.Context, subscribeId int64) ([]*Subscribe, error) UpdateUserSubscribeWithTraffic(ctx context.Context, id, download, upload int64, tx ...*gorm.DB) error QueryResisterUserTotalByDate(ctx context.Context, date time.Time) (int64, error) @@ -87,7 +85,6 @@ type customUserLogicModel interface { QueryAdminUsers(ctx context.Context) ([]*User, error) UpdateUserCache(ctx context.Context, data *User) error UpdateUserSubscribeCache(ctx context.Context, data *Subscribe) error - InsertCommissionLog(ctx context.Context, data *CommissionLog, tx ...*gorm.DB) error QueryActiveSubscriptions(ctx context.Context, subscribeId ...int64) (map[int64]int64, error) FindUserAuthMethods(ctx context.Context, userId int64) ([]*AuthMethods, error) InsertUserAuthMethods(ctx context.Context, data *AuthMethods, tx ...*gorm.DB) error @@ -98,23 +95,25 @@ type customUserLogicModel interface { FindUserAuthMethodByPlatform(ctx context.Context, userId int64, platform string) (*AuthMethods, error) FindOneByEmail(ctx context.Context, email string) (*User, 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) UpdateDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error FindOneDeviceByIdentifier(ctx context.Context, id string) (*Device, error) DeleteDevice(ctx context.Context, id int64, tx ...*gorm.DB) error - - InsertSubscribeLog(ctx context.Context, log *SubscribeLog) error - FilterSubscribeLogList(ctx context.Context, page, size int, filter *SubscribeLogFilterParams) ([]*SubscribeLog, int64, error) - InsertLoginLog(ctx context.Context, log *LoginLog) error - FilterLoginLogList(ctx context.Context, page, size int, filter *LoginLogFilterParams) ([]*LoginLog, int64, error) + InsertDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error ClearSubscribeCache(ctx context.Context, data ...*Subscribe) error + ClearUserCache(ctx context.Context, data ...*User) error - InsertResetSubscribeLog(ctx context.Context, log *ResetSubscribeLog, tx ...*gorm.DB) error - UpdateResetSubscribeLog(ctx context.Context, log *ResetSubscribeLog, tx ...*gorm.DB) error - FindResetSubscribeLog(ctx context.Context, id int64) (*ResetSubscribeLog, error) - DeleteResetSubscribeLog(ctx context.Context, id int64, tx ...*gorm.DB) error - FilterResetSubscribeLogList(ctx context.Context, filter *FilterResetSubscribeLogParams) ([]*ResetSubscribeLog, int64, error) + QueryDailyUserStatisticsList(ctx context.Context, date time.Time) ([]UserStatisticsWithDate, error) + QueryMonthlyUserStatisticsList(ctx context.Context, date time.Time) ([]UserStatisticsWithDate, error) +} + +type UserStatisticsWithDate struct { + Date string + Register int64 + NewOrderUsers int64 + RenewalOrderUsers int64 } // NewModel returns a model for the database table. @@ -124,56 +123,6 @@ func NewModel(conn *gorm.DB, c *redis.Client) Model { } } -func (m *defaultUserModel) getSubscribeCacheKey(data *Subscribe) []string { - if data == nil { - return []string{} - } - var keys []string - if data.Token != "" { - keys = append(keys, fmt.Sprintf("%s%s", cacheUserSubscribeTokenPrefix, data.Token)) - } - if data.UserId != 0 { - keys = append(keys, fmt.Sprintf("%s%d", cacheUserSubscribeUserPrefix, data.UserId)) - } - if data.Id != 0 { - keys = append(keys, fmt.Sprintf("%s%d", cacheUserSubscribeIdPrefix, data.Id)) - } - - if data.SubscribeId != 0 { - var sub *subscribe.Subscribe - err := m.QueryNoCacheCtx(context.Background(), &sub, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&subscribe.Subscribe{}).Where("id = ?", data.SubscribeId).First(&sub).Error - }) - if err != nil { - logger.Error("getUserSubscribeCacheKey", logger.Field("error", err.Error()), logger.Field("subscribeId", data.SubscribeId)) - return keys - } - if sub.Server != "" { - ids := tool.StringToInt64Slice(sub.Server) - for _, id := range ids { - keys = append(keys, fmt.Sprintf("%s%d", config.ServerUserListCacheKey, id)) - } - } - if sub.ServerGroup != "" { - ids := tool.StringToInt64Slice(sub.ServerGroup) - var servers []*server.Server - err = m.QueryNoCacheCtx(context.Background(), &servers, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&server.Server{}).Where("group_id in ?", ids).Find(v).Error - }) - if err != nil { - logger.Error("getUserSubscribeCacheKey", logger.Field("error", err.Error()), logger.Field("subscribeId", data.SubscribeId)) - return keys - } - for _, s := range servers { - keys = append(keys, fmt.Sprintf("%s%d", config.ServerUserListCacheKey, s.Id)) - } - } - } - - return keys - -} - // QueryPageList returns a list of records that meet the conditions. func (m *customUserModel) QueryPageList(ctx context.Context, page, size int, filter *UserFilterParams) ([]*User, int64, error) { var list []*User @@ -195,6 +144,9 @@ func (m *customUserModel) QueryPageList(ctx context.Context, page, size int, fil conn = conn.Joins("LEFT JOIN user_subscribe ON user.id = user_subscribe.user_id"). Where("user_subscribe.subscribe_id =? and `status` IN (0,1)", *filter.SubscribeId) } + if filter.Order != "" { + conn = conn.Order(fmt.Sprintf("user.id %s", filter.Order)) + } } return conn.Model(&User{}).Group("user.id").Count(&total).Limit(size).Offset((page - 1) * size).Preload("UserDevices").Preload("AuthMethods").Find(&list).Error }) @@ -218,33 +170,20 @@ func (m *customUserModel) BatchDeleteUser(ctx context.Context, ids []int64, tx . }, m.batchGetCacheKeys(users...)...) } -// InsertBalanceLog insert BalanceLog into the database. -func (m *customUserModel) InsertBalanceLog(ctx context.Context, data *BalanceLog, tx ...*gorm.DB) error { - return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error { - if len(tx) > 0 { - conn = tx[0] - } - return conn.Create(data).Error - }) -} - -// FindUserBalanceLogList returns a list of records that meet the conditions. -func (m *customUserModel) FindUserBalanceLogList(ctx context.Context, userId int64, page, size int) ([]*BalanceLog, int64, error) { - var list []*BalanceLog - var total int64 - err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error { - - return conn.Model(&BalanceLog{}).Where("`user_id` = ?", userId).Count(&total).Limit(size).Offset((page - 1) * size).Find(&list).Error - }) - return list, total, err -} - func (m *customUserModel) UpdateUserSubscribeWithTraffic(ctx context.Context, id, download, upload int64, tx ...*gorm.DB) error { sub, err := m.FindOneSubscribe(ctx, id) if err != nil { return err } - return m.ExecCtx(ctx, func(conn *gorm.DB) error { + + // 使用 defer 确保更新后清理缓存 + defer func() { + if clearErr := m.ClearSubscribeCacheByModels(ctx, sub); clearErr != nil { + // 记录清理缓存错误 + } + }() + + return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error { if len(tx) > 0 { conn = tx[0] } @@ -252,7 +191,7 @@ func (m *customUserModel) UpdateUserSubscribeWithTraffic(ctx context.Context, id "download": gorm.Expr("download + ?", download), "upload": gorm.Expr("upload + ?", upload), }).Error - }, m.getSubscribeCacheKey(sub)...) + }) } func (m *customUserModel) QueryResisterUserTotalByDate(ctx context.Context, date time.Time) (int64, error) { @@ -292,16 +231,7 @@ func (m *customUserModel) QueryAdminUsers(ctx context.Context) ([]*User, error) } func (m *customUserModel) UpdateUserCache(ctx context.Context, data *User) error { - return m.CachedConn.DelCacheCtx(ctx, m.getCacheKeys(data)...) -} - -func (m *customUserModel) InsertCommissionLog(ctx context.Context, data *CommissionLog, tx ...*gorm.DB) error { - return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error { - if len(tx) > 0 { - conn = tx[0] - } - return conn.Model(&CommissionLog{}).Create(data).Error - }) + return m.ClearUserCache(ctx, data) } func (m *customUserModel) FindOneByReferCode(ctx context.Context, referCode string) (*User, error) { @@ -320,81 +250,77 @@ func (m *customUserModel) FindOneSubscribeDetailsById(ctx context.Context, id in return &data, err } -func (m *customUserModel) InsertResetSubscribeLog(ctx context.Context, log *ResetSubscribeLog, tx ...*gorm.DB) error { - return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error { - if len(tx) > 0 { - conn = tx[0] - } - return conn.Model(&ResetSubscribeLog{}).Create(log).Error - }) -} +// QueryDailyUserStatisticsList Query daily user statistics list for the current month (from 1st to current date) +func (m *customUserModel) QueryDailyUserStatisticsList(ctx context.Context, date time.Time) ([]UserStatisticsWithDate, error) { + var results []UserStatisticsWithDate -func (m *customUserModel) UpdateResetSubscribeLog(ctx context.Context, log *ResetSubscribeLog, tx ...*gorm.DB) error { - return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error { - if len(tx) > 0 { - conn = tx[0] - } - return conn.Model(&ResetSubscribeLog{}).Where("id = ?", log.Id).Updates(log).Error - }) -} + err := m.QueryNoCacheCtx(ctx, &results, func(conn *gorm.DB, v interface{}) error { + firstDay := time.Date(date.Year(), date.Month(), 1, 0, 0, 0, 0, date.Location()) -func (m *customUserModel) FindResetSubscribeLog(ctx context.Context, id int64) (*ResetSubscribeLog, error) { - var data ResetSubscribeLog - err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&ResetSubscribeLog{}).Where("id = ?", id).First(&data).Error - }) - return &data, err -} + // 子查询:统计每天的新用户订单数量 + newOrderSub := conn.Model(&order.Order{}). + Select("DATE_FORMAT(created_at, '%Y-%m-%d') AS date, COUNT(DISTINCT user_id) AS new_order_users"). + Where("is_new = 1 AND created_at BETWEEN ? AND ? AND status IN ?", firstDay, date, []int64{2, 5}). + Group("DATE_FORMAT(created_at, '%Y-%m-%d')") -func (m *customUserModel) DeleteResetSubscribeLog(ctx context.Context, id int64, tx ...*gorm.DB) error { - return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error { - if len(tx) > 0 { - conn = tx[0] - } - return conn.Model(&ResetSubscribeLog{}).Where("id = ?", id).Delete(&ResetSubscribeLog{}).Error - }) -} + // 子查询:统计每天的续费订单数量 + renewalOrderSub := conn.Model(&order.Order{}). + Select("DATE_FORMAT(created_at, '%Y-%m-%d') AS date, COUNT(DISTINCT user_id) AS renewal_order_users"). + Where("is_new = 0 AND created_at BETWEEN ? AND ? AND status IN ?", firstDay, date, []int64{2, 5}). + Group("DATE_FORMAT(created_at, '%Y-%m-%d')") -func (m *customUserModel) FilterResetSubscribeLogList(ctx context.Context, filter *FilterResetSubscribeLogParams) ([]*ResetSubscribeLog, int64, error) { - if filter == nil { - return nil, 0, errors.New("filter params is nil") - } - - var list []*ResetSubscribeLog - var total int64 - - err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error { - query := conn.Model(&ResetSubscribeLog{}) - - // 应用筛选条件 - if filter.UserId != 0 { - query = query.Where("user_id = ?", filter.UserId) - } - if filter.UserSubscribeId != 0 { - query = query.Where("user_subscribe_id = ?", filter.UserSubscribeId) - } - if filter.Type != 0 { - query = query.Where("type = ?", filter.Type) - } - if filter.OrderNo != "" { - query = query.Where("order_no = ?", filter.OrderNo) - } - - // 计算总数 - if err := query.Count(&total).Error; err != nil { - return err - } - - // 应用分页 - if filter.Page > 0 && filter.Size > 0 { - query = query.Offset((filter.Page - 1) * filter.Size) - } - if filter.Size > 0 { - query = query.Limit(filter.Size) - } - - return query.Find(&list).Error + return conn.Model(&User{}). + Select(` + DATE_FORMAT(user.created_at, '%Y-%m-%d') AS date, + COUNT(*) AS register, + IFNULL(MAX(n.new_order_users), 0) AS new_order_users, + IFNULL(MAX(r.renewal_order_users), 0) AS renewal_order_users + `). + Joins("LEFT JOIN (?) AS n ON DATE_FORMAT(user.created_at, '%Y-%m-%d') = n.date", newOrderSub). + Joins("LEFT JOIN (?) AS r ON DATE_FORMAT(user.created_at, '%Y-%m-%d') = r.date", renewalOrderSub). + Where("user.created_at BETWEEN ? AND ?", firstDay, date). + Group("DATE_FORMAT(user.created_at, '%Y-%m-%d')"). + Order("date ASC"). + Scan(v).Error }) - return list, total, err + return results, err +} + +// QueryMonthlyUserStatisticsList Query monthly user statistics list for the past 6 months +func (m *customUserModel) QueryMonthlyUserStatisticsList(ctx context.Context, date time.Time) ([]UserStatisticsWithDate, error) { + var results []UserStatisticsWithDate + + err := m.QueryNoCacheCtx(ctx, &results, func(conn *gorm.DB, v interface{}) error { + // 获取 6 个月前的日期 + sixMonthsAgo := date.AddDate(0, -5, 0) + + // 子查询:每月新订单用户数量 + newOrderSub := conn.Model(&order.Order{}). + Select("DATE_FORMAT(created_at, '%Y-%m') AS date, COUNT(DISTINCT user_id) AS new_order_users"). + Where("is_new = 1 AND created_at >= ? AND status IN ?", sixMonthsAgo, []int64{2, 5}). + Group("DATE_FORMAT(created_at, '%Y-%m')") + + // 子查询:每月续费订单用户数量 + renewalOrderSub := conn.Model(&order.Order{}). + Select("DATE_FORMAT(created_at, '%Y-%m') AS date, COUNT(DISTINCT user_id) AS renewal_order_users"). + Where("is_new = 0 AND created_at >= ? AND status IN ?", sixMonthsAgo, []int64{2, 5}). + Group("DATE_FORMAT(created_at, '%Y-%m')") + + return conn.Model(&User{}). + Select(` + DATE_FORMAT(user.created_at, '%Y-%m') AS date, + COUNT(*) AS register, + IFNULL(MAX(n.new_order_users), 0) AS new_order_users, + IFNULL(MAX(r.renewal_order_users), 0) AS renewal_order_users + `). + Joins("LEFT JOIN (?) AS n ON DATE_FORMAT(user.created_at, '%Y-%m') = n.date", newOrderSub). + Joins("LEFT JOIN (?) AS r ON DATE_FORMAT(user.created_at, '%Y-%m') = r.date", renewalOrderSub). + Where("user.created_at >= ?", sixMonthsAgo). + Group("DATE_FORMAT(user.created_at, '%Y-%m')"). + Order("date ASC"). + Scan(v).Error + }) + + return results, err } diff --git a/internal/model/user/subscribe.go b/internal/model/user/subscribe.go index 0c8d5ba..362fb99 100644 --- a/internal/model/user/subscribe.go +++ b/internal/model/user/subscribe.go @@ -9,7 +9,7 @@ import ( ) func (m *defaultUserModel) UpdateUserSubscribeCache(ctx context.Context, data *Subscribe) error { - return m.CachedConn.DelCacheCtx(ctx, m.getSubscribeCacheKey(data)...) + return m.ClearSubscribeCacheByModels(ctx, data) } // QueryActiveSubscriptions returns the number of active subscriptions. @@ -21,7 +21,7 @@ func (m *defaultUserModel) QueryActiveSubscriptions(ctx context.Context, subscri var result []SubscriptionCount err := m.QueryNoCacheCtx(ctx, &result, func(conn *gorm.DB, v interface{}) error { return conn.Model(&Subscribe{}). - Where("subscribe_id IN ? AND `status` IN ?", subscribeId, []int64{1, 0, 3}). + Where("subscribe_id IN ? AND `status` IN ?", subscribeId, []int64{1, 0}). Select("subscribe_id, COUNT(id) as total"). Group("subscribe_id"). Scan(&result). @@ -55,13 +55,18 @@ func (m *defaultUserModel) FindOneSubscribe(ctx context.Context, id int64) (*Sub return conn.Model(&Subscribe{}).Where("id = ?", id).First(&data).Error }) return &data, err - } func (m *defaultUserModel) FindUsersSubscribeBySubscribeId(ctx context.Context, subscribeId int64) ([]*Subscribe, error) { var data []*Subscribe err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&Subscribe{}).Where("subscribe_id = ? AND `status` IN ?", subscribeId, []int64{1, 0}).Find(&data).Error + err := conn.Model(&Subscribe{}).Where("subscribe_id = ? AND `status` IN ?", subscribeId, []int64{1, 0}).Find(v).Error + + if err != nil { + return err + } + // update user subscribe status + return conn.Model(&Subscribe{}).Where("subscribe_id = ? AND `status` = ?", subscribeId, 0).Update("status", 1).Error }) return data, err } @@ -76,8 +81,12 @@ func (m *defaultUserModel) QueryUserSubscribe(ctx context.Context, userId int64, // 获取当前时间向前推 7 天 sevenDaysAgo := time.Now().Add(-7 * 24 * time.Hour) // 基础条件查询 - conn = conn.Model(&Subscribe{}).Where("`user_id` = ? and `status` IN ?", userId, status) - return conn.Where("`expire_time` > ? OR `finished_at` >= ?", now, sevenDaysAgo). + conn = conn.Model(&Subscribe{}).Where("`user_id` = ?", userId) + if len(status) > 0 { + conn = conn.Where("`status` IN ?", status) + } + // 订阅过期时间大于当前时间或者订阅结束时间大于当前时间 + return conn.Where("`expire_time` > ? OR `finished_at` >= ? OR `expire_time` = ?", now, sevenDaysAgo, time.UnixMilli(0)). Preload("Subscribe"). Find(&list).Error }) @@ -106,12 +115,24 @@ func (m *defaultUserModel) FindOneSubscribeByToken(ctx context.Context, token st // UpdateSubscribe updates a record. func (m *defaultUserModel) UpdateSubscribe(ctx context.Context, data *Subscribe, tx ...*gorm.DB) error { - return m.ExecCtx(ctx, func(conn *gorm.DB) error { + old, err := m.FindOneSubscribe(ctx, data.Id) + if err != nil { + return err + } + + // 使用 defer 确保更新后清理缓存 + defer func() { + if clearErr := m.ClearSubscribeCacheByModels(ctx, old, data); clearErr != nil { + // 记录清理缓存错误 + } + }() + + return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error { if len(tx) > 0 { conn = tx[0] } - return conn.Model(&Subscribe{}).Where("token = ?", data.Token).Save(data).Error - }, m.getSubscribeCacheKey(data)...) + return conn.Model(&Subscribe{}).Where("id = ?", data.Id).Save(data).Error + }) } // DeleteSubscribe deletes a record. @@ -120,22 +141,37 @@ func (m *defaultUserModel) DeleteSubscribe(ctx context.Context, token string, tx if err != nil { return err } - return m.ExecCtx(ctx, func(conn *gorm.DB) error { + + // 使用 defer 确保删除后清理缓存 + defer func() { + if clearErr := m.ClearSubscribeCacheByModels(ctx, data); clearErr != nil { + // 记录清理缓存错误 + } + }() + + return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error { if len(tx) > 0 { conn = tx[0] } return conn.Where("token = ?", token).Delete(&Subscribe{}).Error - }, m.getSubscribeCacheKey(data)...) + }) } // InsertSubscribe insert Subscribe into the database. func (m *defaultUserModel) InsertSubscribe(ctx context.Context, data *Subscribe, tx ...*gorm.DB) error { - return m.ExecCtx(ctx, func(conn *gorm.DB) error { + // 使用 defer 确保插入后清理相关缓存 + defer func() { + if clearErr := m.ClearSubscribeCacheByModels(ctx, data); clearErr != nil { + // 记录清理缓存错误 + } + }() + + return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error { if len(tx) > 0 { conn = tx[0] } return conn.Create(data).Error - }, m.getSubscribeCacheKey(data)...) + }) } func (m *defaultUserModel) DeleteSubscribeById(ctx context.Context, id int64, tx ...*gorm.DB) error { @@ -143,18 +179,22 @@ func (m *defaultUserModel) DeleteSubscribeById(ctx context.Context, id int64, tx if err != nil { return err } - return m.ExecCtx(ctx, func(conn *gorm.DB) error { + + // 使用 defer 确保删除后清理缓存 + defer func() { + if clearErr := m.ClearSubscribeCacheByModels(ctx, data); clearErr != nil { + // 记录清理缓存错误 + } + }() + + return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error { if len(tx) > 0 { conn = tx[0] } return conn.Where("id = ?", id).Delete(&Subscribe{}).Error - }, m.getSubscribeCacheKey(data)...) + }) } func (m *defaultUserModel) ClearSubscribeCache(ctx context.Context, data ...*Subscribe) error { - var keys []string - for _, item := range data { - keys = append(keys, m.getSubscribeCacheKey(item)...) - } - return m.CachedConn.DelCacheCtx(ctx, keys...) + return m.ClearSubscribeCacheByModels(ctx, data...) } diff --git a/internal/model/user/user.go b/internal/model/user/user.go index 5fff3ee..603f98e 100644 --- a/internal/model/user/user.go +++ b/internal/model/user/user.go @@ -2,9 +2,6 @@ package user import ( "time" - - "gorm.io/gorm" - "gorm.io/plugin/soft_delete" ) type User struct { @@ -14,7 +11,9 @@ type User struct { Balance int64 `gorm:"default:0;comment:User Balance"` // User Balance Amount ReferCode string `gorm:"type:varchar(20);default:'';comment:Referral Code"` RefererId int64 `gorm:"index:idx_referer;comment:Referrer ID"` - Commission int64 `gorm:"default:0;comment:Commission"` // Commission Amount + Commission int64 `gorm:"default:0;comment:Commission"` // Commission Amount + ReferralPercentage uint8 `gorm:"default:0;comment:Referral"` // Referral Percentage + OnlyFirstPurchase *bool `gorm:"default:true;not null;comment:Only First Purchase"` // Only First Purchase Referral GiftAmount int64 `gorm:"default:0;comment:User Gift Amount"` Enable *bool `gorm:"default:true;not null;comment:Is Account Enabled"` IsAdmin *bool `gorm:"default:false;not null;comment:Is Admin"` @@ -28,107 +27,33 @@ type User struct { UpdatedAt time.Time `gorm:"comment:Update Time"` } -func (User) TableName() string { - return "user" -} - -type OldUser struct { - Id int64 `gorm:"primaryKey"` - Email string `gorm:"index:idx_email;type:varchar(100);comment:Email"` - //Telephone string `gorm:"index:idx_telephone;type:varchar(20);default:'';comment:Telephone"` - //TelephoneAreaCode string `gorm:"index:idx_telephone;type:varchar(20);default:'';comment:TelephoneAreaCode"` - Password string `gorm:"type:varchar(100);not null;comment:User Password"` - Avatar string `gorm:"type:varchar(200);default:'';comment:User Avatar"` - Balance int64 `gorm:"default:0;comment:User Balance"` // User Balance Amount - Telegram int64 `gorm:"default:null;comment:Telegram Account"` - ReferCode string `gorm:"type:varchar(20);default:'';comment:Referral Code"` - RefererId int64 `gorm:"index:idx_referer;comment:Referrer ID"` - Commission int64 `gorm:"default:0;comment:Commission"` // Commission Amount - GiftAmount int64 `gorm:"default:0;comment:User Gift Amount"` - Enable *bool `gorm:"default:true;not null;comment:Is Account Enabled"` - IsAdmin *bool `gorm:"default:false;not null;comment:Is Admin"` - ValidEmail *bool `gorm:"default:false;not null;comment:Is Email Verified"` - EnableEmailNotify *bool `gorm:"default:false;not null;comment:Enable Email Notifications"` - EnableTelegramNotify *bool `gorm:"default:false;not null;comment:Enable Telegram Notifications"` - EnableBalanceNotify *bool `gorm:"default:false;not null;comment:Enable Balance Change Notifications"` - EnableLoginNotify *bool `gorm:"default:false;not null;comment:Enable Login Notifications"` - EnableSubscribeNotify *bool `gorm:"default:false;not null;comment:Enable Subscription Notifications"` - EnableTradeNotify *bool `gorm:"default:false;not null;comment:Enable Trade Notifications"` - CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` - UpdatedAt time.Time `gorm:"comment:Update Time"` - DeletedAt gorm.DeletedAt `gorm:"default:null;comment:Deletion Time"` - IsDel soft_delete.DeletedAt `gorm:"softDelete:flag,DeletedAtField:DeletedAt;comment:1: Normal 0: Deleted"` // Using `1` and `0` to indicate -} - -func (OldUser) TableName() string { +func (*User) TableName() string { return "user" } type Subscribe struct { - Id int64 `gorm:"primaryKey"` - UserId int64 `gorm:"index:idx_user_id;not null;comment:User ID"` - User User `gorm:"foreignKey:UserId;references:Id"` - OrderId int64 `gorm:"index:idx_order_id;not null;comment:Order ID"` - SubscribeId int64 `gorm:"index:idx_subscribe_id;not null;comment:Subscription ID"` - StartTime time.Time `gorm:"default:CURRENT_TIMESTAMP(3);not null;comment:Subscription Start Time"` - ExpireTime time.Time `gorm:"default:NULL;comment:Subscription Expire Time"` - FinishedAt time.Time `gorm:"default:NULL;comment:Finished Time"` - Traffic int64 `gorm:"default:0;comment:Traffic"` - Download int64 `gorm:"default:0;comment:Download Traffic"` - Upload int64 `gorm:"default:0;comment:Upload Traffic"` - 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"` - Status uint8 `gorm:"type:tinyint(1);default:0;comment:Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired 4: Deducted"` - CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` - UpdatedAt time.Time `gorm:"comment:Update Time"` + Id int64 `gorm:"primaryKey"` + UserId int64 `gorm:"index:idx_user_id;not null;comment:User ID"` + User User `gorm:"foreignKey:UserId;references:Id"` + OrderId int64 `gorm:"index:idx_order_id;not null;comment:Order ID"` + SubscribeId int64 `gorm:"index:idx_subscribe_id;not null;comment:Subscription ID"` + StartTime time.Time `gorm:"default:CURRENT_TIMESTAMP(3);not null;comment:Subscription Start Time"` + ExpireTime time.Time `gorm:"default:NULL;comment:Subscription Expire Time"` + FinishedAt *time.Time `gorm:"default:NULL;comment:Finished Time"` + Traffic int64 `gorm:"default:0;comment:Traffic"` + Download int64 `gorm:"default:0;comment:Download Traffic"` + Upload int64 `gorm:"default:0;comment:Upload Traffic"` + 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"` + Status uint8 `gorm:"type:tinyint(1);default:0;comment:Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired 4: Deducted"` + CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` + UpdatedAt time.Time `gorm:"comment:Update Time"` } -func (Subscribe) TableName() string { +func (*Subscribe) TableName() string { return "user_subscribe" } -type BalanceLog struct { - Id int64 `gorm:"primaryKey"` - UserId int64 `gorm:"index:idx_user_id;not null;comment:User ID"` - Amount int64 `gorm:"not null;comment:Amount"` - Type uint8 `gorm:"type:tinyint(1);not null;comment:Type: 1: Recharge 2: Withdraw 3: Payment 4: Refund 5: Reward"` - OrderId int64 `gorm:"default:null;comment:Order ID"` - Balance int64 `gorm:"not null;comment:Balance"` - CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` -} - -func (BalanceLog) TableName() string { - return "user_balance_log" -} - -type GiftAmountLog struct { - Id int64 `gorm:"primaryKey"` - UserId int64 `gorm:"index:idx_user_id;not null;comment:User ID"` - UserSubscribeId int64 `gorm:"default:null;comment:Deduction User Subscribe ID"` - OrderNo string `gorm:"default:null;comment:Order No."` - Type uint8 `gorm:"type:tinyint(1);not null;comment:Type: 1: Increase 2: Reduce"` - Amount int64 `gorm:"not null;comment:Amount"` - Balance int64 `gorm:"not null;comment:Balance"` - Remark string `gorm:"type:varchar(255);default:'';comment:Remark"` - CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` -} - -func (GiftAmountLog) TableName() string { - return "user_gift_amount_log" -} - -type CommissionLog struct { - Id int64 `gorm:"primaryKey"` - UserId int64 `gorm:"index:idx_user_id;not null;comment:User ID"` - OrderNo string `gorm:"default:null;comment:Order No."` - Amount int64 `gorm:"not null;comment:Amount"` - CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` -} - -func (CommissionLog) TableName() string { - return "user_commission_log" -} - type AuthMethods struct { Id int64 `gorm:"primaryKey"` UserId int64 `gorm:"index:idx_user_id;not null;comment:User ID"` @@ -139,7 +64,7 @@ type AuthMethods struct { UpdatedAt time.Time `gorm:"comment:Update Time"` } -func (AuthMethods) TableName() string { +func (*AuthMethods) TableName() string { return "user_auth_methods" } @@ -155,14 +80,14 @@ type Device struct { UpdatedAt time.Time `gorm:"comment:Update Time"` } -func (Device) TableName() string { +func (*Device) TableName() string { return "user_device" } type DeviceOnlineRecord struct { Id int64 `gorm:"primaryKey"` - UserId int64 `gorm:"comment:User ID"` - Identifier string `gorm:"comment:Device Identifier"` + UserId int64 `gorm:"type:bigint;not null;comment:User ID"` + Identifier string `gorm:"type:varchar(255);not null;comment:Device Identifier"` OnlineTime time.Time `gorm:"comment:Online Time"` // The time when the device goes online OfflineTime time.Time `gorm:"comment:Offline Time"` OnlineSeconds int64 `gorm:"comment:Offline Seconds"` @@ -173,58 +98,3 @@ type DeviceOnlineRecord struct { func (DeviceOnlineRecord) TableName() string { return "user_device_online_record" } - -type LoginLog struct { - Id int64 `gorm:"primaryKey"` - UserId int64 `gorm:"index:idx_user_id;not null;comment:User ID"` - LoginIP string `gorm:"type:varchar(255);not null;comment:Login IP"` - UserAgent string `gorm:"type:text;not null;comment:UserAgent"` - Success *bool `gorm:"default:false;not null;comment:Login Success"` - CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` -} - -func (LoginLog) TableName() string { - return "user_login_log" -} - -type SubscribeLog struct { - Id int64 `gorm:"primaryKey"` - UserId int64 `gorm:"index:idx_user_id;not null;comment:User ID"` - UserSubscribeId int64 `gorm:"index:idx_user_subscribe_id;not null;comment:User Subscribe ID"` - Token string `gorm:"type:varchar(255);not null;comment:Token"` - IP string `gorm:"type:varchar(255);not null;comment:IP"` - UserAgent string `gorm:"type:text;not null;comment:UserAgent"` - CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` -} - -func (SubscribeLog) TableName() string { - return "user_subscribe_log" -} - -const ( - ResetSubscribeTypeAuto uint8 = 1 - ResetSubscribeTypeAdvance uint8 = 2 - ResetSubscribeTypePaid uint8 = 3 -) - -type FilterResetSubscribeLogParams struct { - Page int - Size int - Type uint8 - UserId int64 - OrderNo string - UserSubscribeId int64 -} - -type ResetSubscribeLog struct { - Id int64 `gorm:"primaryKey"` - UserId int64 `gorm:"type:bigint;index:idx_user_id;not null;comment:User ID"` - Type uint8 `gorm:"type:tinyint(1);not null;comment:Type: 1: Auto 2: Advance 3: Paid"` - OrderNo string `gorm:"type:varchar(255);default:null;comment:Order No."` - UserSubscribeId int64 `gorm:"type:bigint;index:idx_user_subscribe_id;not null;comment:User Subscribe ID"` - CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` -} - -func (ResetSubscribeLog) TableName() string { - return "user_reset_subscribe_log" -} diff --git a/internal/server.go b/internal/server.go index 6f47675..64c2704 100644 --- a/internal/server.go +++ b/internal/server.go @@ -8,18 +8,18 @@ import ( "net/http" "time" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/proc" - "github.com/perfect-panel/ppanel-server/pkg/trace" + "github.com/perfect-panel/server/pkg/proc" + "github.com/perfect-panel/server/pkg/trace" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/redis" "github.com/gin-gonic/gin" - "github.com/perfect-panel/ppanel-server/initialize" - "github.com/perfect-panel/ppanel-server/internal/handler" - "github.com/perfect-panel/ppanel-server/internal/middleware" - "github.com/perfect-panel/ppanel-server/internal/svc" + "github.com/perfect-panel/server/initialize" + "github.com/perfect-panel/server/internal/handler" + "github.com/perfect-panel/server/internal/middleware" + "github.com/perfect-panel/server/internal/svc" ) type Service struct { diff --git a/internal/svc/asynq.go b/internal/svc/asynq.go index 07a3003..510ccfb 100644 --- a/internal/svc/asynq.go +++ b/internal/svc/asynq.go @@ -2,7 +2,7 @@ package svc import ( "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/config" + "github.com/perfect-panel/server/internal/config" ) func NewAsynqClient(c config.Config) *asynq.Client { diff --git a/internal/svc/devce.go b/internal/svc/devce.go index 87b6668..2d1fd23 100644 --- a/internal/svc/devce.go +++ b/internal/svc/devce.go @@ -6,11 +6,11 @@ import ( "fmt" "time" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/user" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/device" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/device" + "github.com/perfect-panel/server/pkg/logger" "github.com/pkg/errors" "gorm.io/gorm" ) diff --git a/internal/svc/logger.go b/internal/svc/logger.go index c5e1e94..8291473 100644 --- a/internal/svc/logger.go +++ b/internal/svc/logger.go @@ -1,8 +1,8 @@ package svc import ( - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/pkg/logger" ) func NewLogger(c config.Config) *logger.Logger { diff --git a/internal/svc/serviceContext.go b/internal/svc/serviceContext.go index a6bbd1b..184dc69 100644 --- a/internal/svc/serviceContext.go +++ b/internal/svc/serviceContext.go @@ -3,58 +3,57 @@ package svc import ( "context" - "github.com/perfect-panel/ppanel-server/pkg/device" + "github.com/perfect-panel/server/internal/model/client" + "github.com/perfect-panel/server/internal/model/node" + "github.com/perfect-panel/server/pkg/device" - "github.com/perfect-panel/ppanel-server/internal/model/ads" - "github.com/perfect-panel/ppanel-server/internal/model/cache" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/ads" + "github.com/perfect-panel/server/internal/model/announcement" + "github.com/perfect-panel/server/internal/model/auth" + "github.com/perfect-panel/server/internal/model/coupon" + "github.com/perfect-panel/server/internal/model/document" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/internal/model/order" + "github.com/perfect-panel/server/internal/model/payment" + "github.com/perfect-panel/server/internal/model/subscribe" + "github.com/perfect-panel/server/internal/model/system" + "github.com/perfect-panel/server/internal/model/ticket" + "github.com/perfect-panel/server/internal/model/traffic" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/pkg/limit" + "github.com/perfect-panel/server/pkg/nodeMultiplier" + "github.com/perfect-panel/server/pkg/orm" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/model/announcement" - "github.com/perfect-panel/ppanel-server/internal/model/application" - "github.com/perfect-panel/ppanel-server/internal/model/auth" - "github.com/perfect-panel/ppanel-server/internal/model/coupon" - "github.com/perfect-panel/ppanel-server/internal/model/document" - "github.com/perfect-panel/ppanel-server/internal/model/log" - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/model/payment" - "github.com/perfect-panel/ppanel-server/internal/model/server" - "github.com/perfect-panel/ppanel-server/internal/model/subscribe" - "github.com/perfect-panel/ppanel-server/internal/model/subscribeType" - "github.com/perfect-panel/ppanel-server/internal/model/system" - "github.com/perfect-panel/ppanel-server/internal/model/ticket" - "github.com/perfect-panel/ppanel-server/internal/model/traffic" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/pkg/limit" - "github.com/perfect-panel/ppanel-server/pkg/nodeMultiplier" - "github.com/perfect-panel/ppanel-server/pkg/orm" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) type ServiceContext struct { - DB *gorm.DB - Redis *redis.Client - Config config.Config - Queue *asynq.Client - NodeCache *cache.NodeCacheClient - AuthModel auth.Model - AdsModel ads.Model - LogModel log.Model - UserModel user.Model - OrderModel order.Model - TicketModel ticket.Model - ServerModel server.Model - SystemModel system.Model - CouponModel coupon.Model - PaymentModel payment.Model - DocumentModel document.Model - SubscribeModel subscribe.Model - TrafficLogModel traffic.Model - ApplicationModel application.Model - AnnouncementModel announcement.Model - SubscribeTypeModel subscribeType.Model + DB *gorm.DB + Redis *redis.Client + Config config.Config + Queue *asynq.Client + //NodeCache *cache.NodeCacheClient + AuthModel auth.Model + AdsModel ads.Model + LogModel log.Model + NodeModel node.Model + UserModel user.Model + OrderModel order.Model + ClientModel client.Model + TicketModel ticket.Model + //ServerModel server.Model + SystemModel system.Model + CouponModel coupon.Model + PaymentModel payment.Model + DocumentModel document.Model + SubscribeModel subscribe.Model + TrafficLogModel traffic.Model + AnnouncementModel announcement.Model + Restart func() error TelegramBot *tgbotapi.BotAPI NodeMultiplierManager *nodeMultiplier.Manager @@ -83,26 +82,27 @@ func NewServiceContext(c config.Config) *ServiceContext { } authLimiter := limit.NewPeriodLimit(86400, 15, rds, config.SendCountLimitKeyPrefix, limit.Align()) srv := &ServiceContext{ - DB: db, - Redis: rds, - Config: c, - Queue: NewAsynqClient(c), - NodeCache: cache.NewNodeCacheClient(rds), - AuthLimiter: authLimiter, - AdsModel: ads.NewModel(db, rds), - LogModel: log.NewModel(db), - AuthModel: auth.NewModel(db, rds), - UserModel: user.NewModel(db, rds), - OrderModel: order.NewModel(db, rds), - TicketModel: ticket.NewModel(db, rds), - ServerModel: server.NewModel(db, rds), + DB: db, + Redis: rds, + Config: c, + Queue: NewAsynqClient(c), + //NodeCache: cache.NewNodeCacheClient(rds), + AuthLimiter: authLimiter, + AdsModel: ads.NewModel(db, rds), + LogModel: log.NewModel(db), + NodeModel: node.NewModel(db, rds), + AuthModel: auth.NewModel(db, rds), + UserModel: user.NewModel(db, rds), + OrderModel: order.NewModel(db, rds), + ClientModel: client.NewSubscribeApplicationModel(db), + TicketModel: ticket.NewModel(db, rds), + //ServerModel: server.NewModel(db, rds), SystemModel: system.NewModel(db, rds), CouponModel: coupon.NewModel(db, rds), PaymentModel: payment.NewModel(db, rds), DocumentModel: document.NewModel(db, rds), SubscribeModel: subscribe.NewModel(db, rds), TrafficLogModel: traffic.NewModel(db), - ApplicationModel: application.NewModel(db, rds), AnnouncementModel: announcement.NewModel(db, rds), } srv.DeviceManager = NewDeviceManager(srv) diff --git a/internal/types/types.go b/internal/types/types.go index 27a4d31..89d404c 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -32,79 +32,9 @@ type Announcement struct { UpdatedAt int64 `json:"updated_at"` } -type AppAuthCheckRequest struct { - Method string `json:"method" validate:"required" validate:"required,oneof=device email mobile"` - Account string `json:"account"` - Identifier string `json:"identifier" validate:"required"` - UserAgent string `json:"user_agent" validate:"required,oneof=windows mac linux android ios harmony"` - AreaCode string `json:"area_code"` -} - -type AppAuthCheckResponse struct { - Status bool -} - -type AppAuthRequest struct { - Method string `json:"method" validate:"required" validate:"required,oneof=device email mobile"` - Account string `json:"account"` - Password string `json:"password"` - Identifier string `json:"identifier" validate:"required"` - UserAgent string `json:"user_agent" validate:"required,oneof=windows mac linux android ios harmony"` - Code string `json:"code"` - Invite string `json:"invite"` - AreaCode string `json:"area_code"` - CfToken string `json:"cf_token,optional"` -} - -type AppAuthRespone struct { - Token string `json:"token"` -} - -type AppConfigRequest struct { - UserAgent string `json:"user_agent" validate:"required,oneof=windows mac linux android ios harmony"` -} - -type AppConfigResponse struct { - EncryptionKey string `json:"encryption_key"` - EncryptionMethod string `json:"encryption_method"` - Domains []string `json:"domains"` - StartupPicture string `json:"startup_picture"` - StartupPictureSkipTime int64 `json:"startup_picture_skip_time"` - Application AppInfo `json:"applications"` - OfficialEmail string `json:"official_email"` - OfficialWebsite string `json:"official_website"` - OfficialTelegram string `json:"official_telegram"` - OfficialTelephone string `json:"official_telephone"` - InvitationLink string `json:"invitation_link"` - KrWebsiteId string `json:"kr_website_id"` -} - -type AppInfo struct { - Id int64 `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Url string `json:"url"` - Version string `json:"version"` - VersionReview string `json:"version_review"` - VersionDescription string `json:"version_description"` - IsDefault bool `json:"is_default"` -} - -type AppRuleGroupListResponse struct { - Total int64 `json:"total"` - List []ServerRuleGroup `json:"list"` -} - -type AppSendCodeRequest struct { - Method string `json:"method" validate:"required" validate:"required,oneof=email mobile"` - Account string `json:"account"` - AreaCode string `json:"area_code"` - CfToken string `json:"cf_token,optional"` -} - -type AppSendCodeRespone struct { - Status bool `json:"status"` - Code string `json:"code,omitempty"` +type AnyTLS struct { + Port int `json:"port" validate:"required"` + SecurityConfig SecurityConfig `json:"security_config"` } type AppUserSubcbribe struct { @@ -120,43 +50,25 @@ type AppUserSubcbribe struct { } type AppUserSubscbribeNode struct { - Id int64 `json:"id"` - Name string `json:"name"` - Uuid string `json:"uuid"` - Protocol string `json:"protocol"` - RelayMode string `json:"relay_mode"` - RelayNode string `json:"relay_node"` - ServerAddr string `json:"server_addr"` - SpeedLimit int `json:"speed_limit"` - Tags []string `json:"tags"` - Traffic int64 `json:"traffic"` - TrafficRatio float64 `json:"traffic_ratio"` - Upload int64 `json:"upload"` - Config string `json:"config"` - Country string `json:"country"` - City string `json:"city"` - Latitude string `json:"latitude"` - Longitude string `json:"longitude"` - LatitudeCountry string `json:"latitudeCountry"` - LongitudeCountry string `json:"longitudeCountry"` - CreatedAt int64 `json:"created_at"` - Download int64 `json:"download"` -} - -type AppUserSubscbribeNodeRequest struct { - Id int64 `form:"id" validate:"required"` -} - -type AppUserSubscbribeNodeResponse struct { - List []AppUserSubscbribeNode `json:"list"` -} - -type AppUserSubscbribeResponse struct { - List []AppUserSubcbribe `json:"list"` -} - -type AppUserSubscribeRequest struct { - ContainsNodes *bool `form:"contains_nodes"` + Id int64 `json:"id"` + Name string `json:"name"` + Uuid string `json:"uuid"` + Protocol string `json:"protocol"` + RelayMode string `json:"relay_mode"` + RelayNode string `json:"relay_node"` + ServerAddr string `json:"server_addr"` + SpeedLimit int `json:"speed_limit"` + Tags []string `json:"tags"` + Traffic int64 `json:"traffic"` + TrafficRatio float64 `json:"traffic_ratio"` + Upload int64 `json:"upload"` + Config string `json:"config"` + Country string `json:"country"` + City string `json:"city"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + CreatedAt int64 `json:"created_at"` + Download int64 `json:"download"` } type AppleLoginCallbackRequest struct { @@ -173,15 +85,6 @@ type Application struct { SubscribeType string `json:"subscribe_type"` } -type ApplicationConfig struct { - AppId int64 `json:"app_id"` - EncryptionKey string `json:"encryption_key"` - EncryptionMethod string `json:"encryption_method"` - Domains []string `json:"domains" validate:"required"` - StartupPicture string `json:"startup_picture"` - StartupPictureSkipTime int64 `json:"startup_picture_skip_time"` -} - type ApplicationPlatform struct { IOS []*ApplicationVersion `json:"ios,omitempty"` MacOS []*ApplicationVersion `json:"macos,omitempty"` @@ -215,6 +118,7 @@ type ApplicationVersion struct { type AuthConfig struct { Mobile MobileAuthenticateConfig `json:"mobile"` Email EmailAuthticateConfig `json:"email"` + Device DeviceAuthticateConfig `json:"device"` Register PubilcRegisterConfig `json:"register"` } @@ -225,6 +129,15 @@ type AuthMethodConfig struct { Enabled bool `json:"enabled"` } +type BalanceLog struct { + Type uint16 `json:"type"` + UserId int64 `json:"user_id"` + Amount int64 `json:"amount"` + OrderNo string `json:"order_no,omitempty"` + Balance int64 `json:"balance"` + Timestamp int64 `json:"timestamp"` +} + type BatchDeleteCouponRequest struct { Ids []int64 `json:"ids" validate:"required"` } @@ -233,14 +146,6 @@ type BatchDeleteDocumentRequest struct { Ids []int64 `json:"ids" validate:"required"` } -type BatchDeleteNodeGroupRequest struct { - Ids []int64 `json:"ids" validate:"required"` -} - -type BatchDeleteNodeRequest struct { - Ids []int64 `json:"ids" validate:"required"` -} - type BatchDeleteSubscribeGroupRequest struct { Ids []int64 `json:"ids" validate:"required"` } @@ -253,6 +158,26 @@ type BatchDeleteUserRequest struct { Ids []int64 `json:"ids" validate:"required"` } +type BatchSendEmailTask struct { + Id int64 `json:"id"` + Subject string `json:"subject"` + Content string `json:"content"` + Recipients string `json:"recipients"` + Scope int8 `json:"scope"` + RegisterStartTime int64 `json:"register_start_time"` + RegisterEndTime int64 `json:"register_end_time"` + Additional string `json:"additional"` + Scheduled int64 `json:"scheduled"` + Interval uint8 `json:"interval"` + Limit uint64 `json:"limit"` + Status uint8 `json:"status"` + Errors string `json:"errors"` + Total uint64 `json:"total"` + Current uint64 `json:"current"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} + type BindOAuthCallbackRequest struct { Method string `json:"method"` Callback interface{} `json:"callback"` @@ -307,17 +232,11 @@ type CloseOrderRequest struct { } type CommissionLog struct { - Id int64 `json:"id"` + Type uint16 `json:"type"` UserId int64 `json:"user_id"` - OrderNo string `json:"order_no"` Amount int64 `json:"amount"` - CreatedAt int64 `json:"created_at"` -} - -type ConnectionRecords struct { - CurrentContinuousDays int64 `json:"current_continuous_days"` - HistoryContinuousDays int64 `json:"history_continuous_days"` - LongestSingleConnection int64 `json:"longest_single_connection"` + OrderNo string `json:"order_no"` + Timestamp int64 `json:"timestamp"` } type Coupon struct { @@ -353,21 +272,16 @@ type CreateAnnouncementRequest struct { Content string `json:"content" validate:"required"` } -type CreateApplicationRequest struct { - Icon string `json:"icon"` - Name string `json:"name"` - Description string `json:"description"` - SubscribeType string `json:"subscribe_type"` - Platform ApplicationPlatform `json:"platform"` -} - -type CreateApplicationVersionRequest struct { - Url string `json:"url"` - Version string `json:"version" validate:"required"` - Description string `json:"description"` - Platform string `json:"platform" validate:"required,oneof=windows mac linux android ios harmony"` - IsDefault bool `json:"is_default"` - ApplicationId int64 `json:"application_id" validate:"required"` +type CreateBatchSendEmailTaskRequest struct { + Subject string `json:"subject"` + Content string `json:"content"` + Scope int8 `json:"scope"` + RegisterStartTime int64 `json:"register_start_time,omitempty"` + RegisterEndTime int64 `json:"register_end_time,omitempty"` + Additional string `json:"additional,omitempty"` + Scheduled int64 `json:"scheduled,omitempty"` + Interval uint8 `json:"interval,omitempty"` + Limit uint64 `json:"limit,omitempty"` } type CreateCouponRequest struct { @@ -391,26 +305,14 @@ type CreateDocumentRequest struct { Show *bool `json:"show"` } -type CreateNodeGroupRequest struct { - Name string `json:"name" validate:"required"` - Description string `json:"description"` -} - type CreateNodeRequest struct { - Name string `json:"name" validate:"required"` - Tags []string `json:"tags"` - Country string `json:"country"` - City string `json:"city"` - ServerAddr string `json:"server_addr" validate:"required"` - RelayMode string `json:"relay_mode"` - RelayNode []NodeRelay `json:"relay_node"` - SpeedLimit int `json:"speed_limit"` - TrafficRatio float32 `json:"traffic_ratio"` - GroupId int64 `json:"group_id"` - Protocol string `json:"protocol" validate:"required"` - Config interface{} `json:"config" validate:"required"` - Enable *bool `json:"enable"` - Sort int64 `json:"sort"` + Name string `json:"name"` + Tags []string `json:"tags,omitempty"` + Port uint16 `json:"port"` + Address string `json:"address"` + ServerId int64 `json:"server_id"` + Protocol string `json:"protocol"` + Enabled *bool `json:"enabled"` } type CreateOrderRequest struct { @@ -443,12 +345,36 @@ type CreatePaymentMethodRequest struct { Enable *bool `json:"enable" validate:"required"` } -type CreateRuleGroupRequest struct { - Name string `json:"name" validate:"required"` - Icon string `json:"icon"` - Tags []string `json:"tags"` - Rules string `json:"rules"` - Enable bool `json:"enable"` +type CreateQuotaTaskRequest struct { + Subscribers []int64 `json:"subscribers"` + IsActive *bool `json:"is_active"` + StartTime int64 `json:"start_time"` + EndTime int64 `json:"end_time"` + ResetTraffic bool `json:"reset_traffic"` + Days uint64 `json:"days"` + GiftType uint8 `json:"gift_type"` + GiftValue uint64 `json:"gift_value"` +} + +type CreateServerRequest struct { + Name string `json:"name"` + Country string `json:"country,omitempty"` + City string `json:"city,omitempty"` + Address string `json:"address"` + Sort int `json:"sort,omitempty"` + Protocols []Protocol `json:"protocols"` +} + +type CreateSubscribeApplicationRequest struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Icon string `json:"icon,omitempty"` + Scheme string `json:"scheme,omitempty"` + UserAgent string `json:"user_agent"` + IsDefault bool `json:"is_default"` + SubscribeTemplate string `json:"template"` + OutputFormat string `json:"output_format"` + DownloadLink DownloadLink `json:"download_link"` } type CreateSubscribeGroupRequest struct { @@ -458,6 +384,7 @@ type CreateSubscribeGroupRequest struct { type CreateSubscribeRequest struct { Name string `json:"name" validate:"required"` + Language string `json:"language"` Description string `json:"description"` UnitPrice int64 `json:"unit_price"` UnitTime string `json:"unit_time"` @@ -468,9 +395,8 @@ type CreateSubscribeRequest struct { SpeedLimit int64 `json:"speed_limit"` DeviceLimit int64 `json:"device_limit"` Quota int64 `json:"quota"` - GroupId int64 `json:"group_id"` - ServerGroup []int64 `json:"server_group"` - Server []int64 `json:"server"` + Nodes []int64 `json:"nodes"` + NodeTags []string `json:"node_tags"` Show *bool `json:"show"` Sell *bool `json:"sell"` DeductionRatio int64 `json:"deduction_ratio"` @@ -493,18 +419,20 @@ type CreateUserAuthMethodRequest struct { } type CreateUserRequest struct { - Email string `json:"email"` - Telephone string `json:"telephone"` - TelephoneAreaCode string `json:"telephone_area_code"` - Password string `json:"password"` - ProductId int64 `json:"product_id"` - Duration int64 `json:"duration"` - RefererUser string `json:"referer_user"` - ReferCode string `json:"refer_code"` - Balance int64 `json:"balance"` - Commission int64 `json:"commission"` - GiftAmount int64 `json:"gift_amount"` - IsAdmin bool `json:"is_admin"` + Email string `json:"email"` + Telephone string `json:"telephone"` + TelephoneAreaCode string `json:"telephone_area_code"` + Password string `json:"password"` + ProductId int64 `json:"product_id"` + Duration int64 `json:"duration"` + ReferralPercentage uint8 `json:"referral_percentage"` + OnlyFirstPurchase bool `json:"only_first_purchase"` + RefererUser string `json:"referer_user"` + ReferCode string `json:"refer_code"` + Balance int64 `json:"balance"` + Commission int64 `json:"commission"` + GiftAmount int64 `json:"gift_amount"` + IsAdmin bool `json:"is_admin"` } type CreateUserSubscribeRequest struct { @@ -537,11 +465,6 @@ type CurrencyConfig struct { CurrencySymbol string `json:"currency_symbol"` } -type DeleteAccountRequest struct { - Method string `json:"method" validate:"required" validate:"required,oneof=email telephone device"` - Code string `json:"code"` -} - type DeleteAdsRequest struct { Id int64 `json:"id"` } @@ -550,14 +473,6 @@ type DeleteAnnouncementRequest struct { Id int64 `json:"id" validate:"required"` } -type DeleteApplicationRequest struct { - Id int64 `json:"id" validate:"required"` -} - -type DeleteApplicationVersionRequest struct { - Id int64 `json:"id" validate:"required"` -} - type DeleteCouponRequest struct { Id int64 `json:"id" validate:"required"` } @@ -566,20 +481,20 @@ type DeleteDocumentRequest struct { Id int64 `json:"id" validate:"required"` } -type DeleteNodeGroupRequest struct { - Id int64 `json:"id" validate:"required"` -} - type DeleteNodeRequest struct { - Id int64 `json:"id" validate:"required"` + Id int64 `json:"id"` } type DeletePaymentMethodRequest struct { Id int64 `json:"id" validate:"required"` } -type DeleteRuleGroupRequest struct { - Id int64 `json:"id" validate:"required"` +type DeleteServerRequest struct { + Id int64 `json:"id"` +} + +type DeleteSubscribeApplicationRequest struct { + Id int64 `json:"id"` } type DeleteSubscribeGroupRequest struct { @@ -603,6 +518,20 @@ type DeleteUserSubscribeRequest struct { UserSubscribeId int64 `json:"user_subscribe_id"` } +type DeviceAuthticateConfig struct { + Enable bool `json:"enable"` + ShowAds bool `json:"show_ads"` + EnableSecurity bool `json:"enable_security"` + OnlyRealDevice bool `json:"only_real_device"` +} + +type DeviceLoginRequest struct { + 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"` +} + type Document struct { Id int64 `json:"id"` Title string `json:"title"` @@ -613,6 +542,15 @@ type Document struct { UpdatedAt int64 `json:"updated_at"` } +type DownloadLink struct { + IOS string `json:"ios,omitempty"` + Android string `json:"android,omitempty"` + Windows string `json:"windows,omitempty"` + Mac string `json:"mac,omitempty"` + Linux string `json:"linux,omitempty"` + Harmony string `json:"harmony,omitempty"` +} + type EPayNotifyRequest struct { Pid int64 `json:"pid" form:"pid"` TradeNo string `json:"trade_no" form:"trade_no"` @@ -633,6 +571,149 @@ type EmailAuthticateConfig struct { DomainSuffixList string `json:"domain_suffix_list"` } +type FilterBalanceLogRequest struct { + FilterLogParams + UserId int64 `form:"user_id,optional"` +} + +type FilterBalanceLogResponse struct { + Total int64 `json:"total"` + List []BalanceLog `json:"list"` +} + +type FilterCommissionLogRequest struct { + FilterLogParams + UserId int64 `form:"user_id,optional"` +} + +type FilterCommissionLogResponse struct { + Total int64 `json:"total"` + List []CommissionLog `json:"list"` +} + +type FilterEmailLogResponse struct { + Total int64 `json:"total"` + List []MessageLog `json:"list"` +} + +type FilterGiftLogRequest struct { + FilterLogParams + UserId int64 `form:"user_id,optional"` +} + +type FilterGiftLogResponse struct { + Total int64 `json:"total"` + List []GiftLog `json:"list"` +} + +type FilterLogParams struct { + Page int `form:"page"` + Size int `form:"size"` + Date string `form:"date,optional"` + Search string `form:"search,optional"` +} + +type FilterLoginLogRequest struct { + FilterLogParams + UserId int64 `form:"user_id,optional"` +} + +type FilterLoginLogResponse struct { + Total int64 `json:"total"` + List []LoginLog `json:"list"` +} + +type FilterMobileLogResponse struct { + Total int64 `json:"total"` + List []MessageLog `json:"list"` +} + +type FilterNodeListRequest struct { + Page int `form:"page"` + Size int `form:"size"` + Search string `form:"search,omitempty"` +} + +type FilterNodeListResponse struct { + Total int64 `json:"total"` + List []Node `json:"list"` +} + +type FilterRegisterLogRequest struct { + FilterLogParams + UserId int64 `form:"user_id,optional"` +} + +type FilterRegisterLogResponse struct { + Total int64 `json:"total"` + List []RegisterLog `json:"list"` +} + +type FilterResetSubscribeLogRequest struct { + FilterLogParams + UserSubscribeId int64 `form:"user_subscribe_id,optional"` +} + +type FilterResetSubscribeLogResponse struct { + Total int64 `json:"total"` + List []ResetSubscribeLog `json:"list"` +} + +type FilterServerListRequest struct { + Page int `form:"page"` + Size int `form:"size"` + Search string `form:"search,omitempty"` +} + +type FilterServerListResponse struct { + Total int64 `json:"total"` + List []Server `json:"list"` +} + +type FilterServerTrafficLogRequest struct { + FilterLogParams + ServerId int64 `form:"server_id,optional"` +} + +type FilterServerTrafficLogResponse struct { + Total int64 `json:"total"` + List []ServerTrafficLog `json:"list"` +} + +type FilterSubscribeLogRequest struct { + FilterLogParams + UserId int64 `form:"user_id,optional"` + UserSubscribeId int64 `form:"user_subscribe_id,optional"` +} + +type FilterSubscribeLogResponse struct { + Total int64 `json:"total"` + List []SubscribeLog `json:"list"` +} + +type FilterSubscribeTrafficRequest struct { + FilterLogParams + UserId int64 `form:"user_id,optional"` + UserSubscribeId int64 `form:"user_subscribe_id,optional"` +} + +type FilterSubscribeTrafficResponse struct { + Total int64 `json:"total"` + List []UserSubscribeTrafficLog `json:"list"` +} + +type FilterTrafficLogDetailsRequest struct { + FilterLogParams + ServerId int64 `form:"server_id,optional"` + SubscribeId int64 `form:"subscribe_id,optional"` + UserId int64 `form:"user_id,optional"` +} + +type FilterTrafficLogDetailsResponse struct { + Total int64 `json:"total"` + List []TrafficLogDetails `json:"list"` +} + type Follow struct { Id int64 `json:"id"` TicketId int64 `json:"ticket_id"` @@ -685,11 +766,6 @@ type GetAnnouncementRequest struct { Id int64 `form:"id" validate:"required"` } -type GetAppcationResponse struct { - Config ApplicationConfig `json:"config"` - Applications []ApplicationResponseInfo `json:"applications"` -} - type GetAuthMethodConfigRequest struct { Method string `form:"method"` } @@ -702,6 +778,29 @@ type GetAvailablePaymentMethodsResponse struct { List []PaymentMethod `json:"list"` } +type GetBatchSendEmailTaskListRequest struct { + Page int `form:"page"` + Size int `form:"size"` + Scope *int8 `form:"scope,omitempty"` + Status *uint8 `form:"status,omitempty"` +} + +type GetBatchSendEmailTaskListResponse struct { + Total int64 `json:"total"` + List []BatchSendEmailTask `json:"list"` +} + +type GetBatchSendEmailTaskStatusRequest struct { + Id int64 `json:"id"` +} + +type GetBatchSendEmailTaskStatusResponse struct { + Status uint8 `json:"status"` + Current int64 `json:"current"` + Total int64 `json:"total"` + Errors string `json:"errors"` +} + type GetCouponListRequest struct { Page int64 `form:"page" validate:"required"` Size int64 `form:"size" validate:"required"` @@ -718,6 +817,11 @@ type GetDetailRequest struct { Id int64 `form:"id" validate:"required"` } +type GetDeviceListResponse struct { + List []UserDevice `json:"list"` + Total int64 `json:"total"` +} + type GetDocumentDetailRequest struct { Id int64 `json:"id" validate:"required"` } @@ -757,14 +861,10 @@ type GetLoginLogResponse struct { } type GetMessageLogListRequest struct { - Page int `form:"page"` - Size int `form:"size"` - Type string `form:"type"` - Platform string `form:"platform,omitempty"` - To string `form:"to,omitempty"` - Subject string `form:"subject,omitempty"` - Content string `form:"content,omitempty"` - Status int `form:"status,omitempty"` + Page int `form:"page"` + Size int `form:"size"` + Type uint8 `form:"type"` + Search string `form:"search,optional"` } type GetMessageLogListResponse struct { @@ -772,36 +872,10 @@ type GetMessageLogListResponse struct { List []MessageLog `json:"list"` } -type GetNodeDetailRequest struct { - Id int64 `form:"id" validate:"required"` -} - -type GetNodeGroupListResponse struct { - Total int64 `json:"total"` - List []ServerGroup `json:"list"` -} - type GetNodeMultiplierResponse struct { Periods []TimePeriod `json:"periods"` } -type GetNodeServerListRequest struct { - Page int `form:"page" validate:"required"` - Size int `form:"size" validate:"required"` - Tag string `form:"tag,omitempty"` - GroupId int64 `form:"group_id,omitempty"` - Search string `form:"search,omitempty"` -} - -type GetNodeServerListResponse struct { - Total int64 `json:"total"` - List []Server `json:"list"` -} - -type GetNodeTagListResponse struct { - Tags []string `json:"tags"` -} - type GetOAuthMethodsResponse struct { Methods []UserAuthMethod `json:"methods"` } @@ -833,9 +907,14 @@ type GetPaymentMethodListResponse struct { List []PaymentMethodDetail `json:"list"` } -type GetRuleGroupResponse struct { - Total int64 `json:"total"` - List []ServerRuleGroup `json:"list"` +type GetPreSendEmailCountRequest struct { + Scope int8 `json:"scope"` + RegisterStartTime int64 `json:"register_start_time,omitempty"` + RegisterEndTime int64 `json:"register_end_time,omitempty"` +} + +type GetPreSendEmailCountResponse struct { + Count int64 `json:"count"` } type GetServerConfigRequest struct { @@ -848,6 +927,14 @@ type GetServerConfigResponse struct { Config interface{} `json:"config"` } +type GetServerProtocolsRequest struct { + Id int64 `form:"id"` +} + +type GetServerProtocolsResponse struct { + Protocols []Protocol `json:"protocols"` +} + type GetServerUserListRequest struct { ServerCommon } @@ -863,6 +950,21 @@ type GetStatResponse struct { Protocol []string `json:"protocol"` } +type GetSubscribeApplicationListRequest struct { + Page int `form:"page"` + Size int `form:"size"` +} + +type GetSubscribeApplicationListResponse struct { + Total int64 `json:"total"` + List []SubscribeApplication `json:"list"` +} + +type GetSubscribeClientResponse struct { + Total int64 `json:"total"` + List []SubscribeClient `json:"list"` +} + type GetSubscribeDetailsRequest struct { Id int64 `form:"id" validate:"required"` } @@ -873,10 +975,10 @@ type GetSubscribeGroupListResponse struct { } type GetSubscribeListRequest struct { - Page int64 `form:"page" validate:"required"` - Size int64 `form:"size" validate:"required"` - GroupId int64 `form:"group_id,omitempty"` - Search string `form:"search,omitempty"` + Page int64 `form:"page" validate:"required"` + Size int64 `form:"size" validate:"required"` + Language string `form:"language,omitempty"` + Search string `form:"search,omitempty"` } type GetSubscribeListResponse struct { @@ -894,6 +996,10 @@ type GetSubscribeLogResponse struct { Total int64 `json:"total"` } +type GetSubscriptionRequest struct { + Language string `form:"language"` +} + type GetSubscriptionResponse struct { List []Subscribe `json:"list"` } @@ -952,11 +1058,6 @@ type GetUserLoginLogsResponse struct { Total int64 `json:"total"` } -type GetUserOnlineTimeStatisticsResponse struct { - WeeklyStats []WeeklyStat `json:"weekly_stats"` - ConnectionRecords ConnectionRecords `json:"connection_records"` -} - type GetUserSubscribeByIdRequest struct { Id int64 `form:"id" validate:"required"` } @@ -996,6 +1097,17 @@ type GetUserSubscribeLogsResponse struct { Total int64 `json:"total"` } +type GetUserSubscribeResetTrafficLogsRequest struct { + Page int `form:"page"` + Size int `form:"size"` + UserSubscribeId int64 `form:"user_subscribe_id"` +} + +type GetUserSubscribeResetTrafficLogsResponse struct { + List []ResetSubscribeTrafficLog `json:"list"` + Total int64 `json:"total"` +} + type GetUserSubscribeTrafficLogsRequest struct { Page int `form:"page"` Size int `form:"size"` @@ -1026,11 +1138,26 @@ type GetUserTicketListResponse struct { List []Ticket `json:"list"` } +type GiftLog struct { + Type uint16 `json:"type"` + UserId int64 `json:"user_id"` + OrderNo string `json:"order_no"` + SubscribeId int64 `json:"subscribe_id"` + Amount int64 `json:"amount"` + Balance int64 `json:"balance"` + Remark string `json:"remark,omitempty"` + Timestamp int64 `json:"timestamp"` +} + type GoogleLoginCallbackRequest struct { Code string `form:"code"` State string `form:"state"` } +type HasMigrateSeverNodeResponse struct { + HasMigrate bool `json:"has_migrate"` +} + type Hysteria2 struct { Port int `json:"port" validate:"required"` HopPorts string `json:"hop_ports" validate:"required"` @@ -1053,20 +1180,39 @@ type LogResponse struct { List interface{} `json:"list"` } +type LogSetting struct { + AutoClear *bool `json:"auto_clear"` + ClearDays int64 `json:"clear_days"` +} + +type LoginLog struct { + UserId int64 `json:"user_id"` + Method string `json:"method"` + LoginIP string `json:"login_ip"` + UserAgent string `json:"user_agent"` + Success bool `json:"success"` + Timestamp int64 `json:"timestamp"` +} + type LoginResponse struct { Token string `json:"token"` } type MessageLog struct { - Id int64 `json:"id"` - Type string `json:"type"` - Platform string `json:"platform"` - To string `json:"to"` - Subject string `json:"subject"` - Content string `json:"content"` - Status int `json:"status"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` + Id int64 `json:"id"` + Type uint8 `json:"type"` + Platform string `json:"platform"` + To string `json:"to"` + Subject string `json:"subject"` + Content interface{} `json:"content"` + Status uint8 `json:"status"` + CreatedAt int64 `json:"created_at"` +} + +type MigrateServerNodeResponse struct { + Succee uint64 `json:"succee"` + Fail uint64 `json:"fail"` + Message string `json:"message,omitempty"` } type MobileAuthenticateConfig struct { @@ -1075,10 +1221,44 @@ type MobileAuthenticateConfig struct { Whitelist []string `json:"whitelist"` } +type Node struct { + Id int64 `json:"id"` + Name string `json:"name"` + Tags []string `json:"tags"` + Port uint16 `json:"port"` + Address string `json:"address"` + ServerId int64 `json:"server_id"` + Protocol string `json:"protocol"` + Enabled *bool `json:"enabled"` + Sort int `json:"sort,omitempty"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} + type NodeConfig struct { - NodeSecret string `json:"node_secret"` - NodePullInterval int64 `json:"node_pull_interval"` - NodePushInterval int64 `json:"node_push_interval"` + NodeSecret string `json:"node_secret"` + NodePullInterval int64 `json:"node_pull_interval"` + NodePushInterval int64 `json:"node_push_interval"` + TrafficReportThreshold int64 `json:"traffic_report_threshold"` + IPStrategy string `json:"ip_strategy"` + DNS []NodeDNS `json:"dns"` + Block []string `json:"block"` + Outbound []NodeOutbound `json:"outbound"` +} + +type NodeDNS struct { + Proto string `json:"proto"` + Address string `json:"address"` + Domains []string `json:"domains"` +} + +type NodeOutbound struct { + Name string `json:"name"` + Protocol string `json:"protocol"` + Address string `json:"address"` + Port int64 `json:"port"` + Password string `json:"password"` + Rules []string `json:"rules"` } type NodeRelay struct { @@ -1087,18 +1267,6 @@ type NodeRelay struct { Prefix string `json:"prefix"` } -type NodeSortRequest struct { - Sort []SortItem `json:"sort"` -} - -type NodeStatus struct { - Online interface{} `json:"online"` - Cpu float64 `json:"cpu"` - Mem float64 `json:"mem"` - Disk float64 `json:"disk"` - UpdatedAt int64 `json:"updated_at"` -} - type OAthLoginRequest struct { Method string `json:"method" validate:"required"` // google, facebook, apple, telegram, github etc. Redirect string `json:"redirect"` @@ -1235,6 +1403,7 @@ type PortalPurchaseRequest struct { SubscribeId int64 `json:"subscribe_id"` Quantity int64 `json:"quantity"` Coupon string `json:"coupon,omitempty"` + InviteCode string `json:"invite_code,omitempty"` TurnstileToken string `json:"turnstile_token,omitempty"` } @@ -1280,10 +1449,73 @@ type PreUnsubscribeResponse struct { DeductionAmount int64 `json:"deduction_amount"` } +type PreViewNodeMultiplierResponse struct { + CurrentTime string `json:"current_time"` + Ratio float32 `json:"ratio"` +} + +type PreviewSubscribeTemplateRequest struct { + Id int64 `form:"id"` +} + +type PreviewSubscribeTemplateResponse struct { + Template string `json:"template"` // 预览的模板内容 +} + type PrivacyPolicyConfig struct { PrivacyPolicy string `json:"privacy_policy"` } +type Protocol struct { + Type string `json:"type"` + Port uint16 `json:"port"` + Enable bool `json:"enable"` + Security string `json:"security,omitempty"` + SNI string `json:"sni,omitempty"` + AllowInsecure bool `json:"allow_insecure,omitempty"` + Fingerprint string `json:"fingerprint,omitempty"` + RealityServerAddr string `json:"reality_server_addr,omitempty"` + RealityServerPort int `json:"reality_server_port,omitempty"` + RealityPrivateKey string `json:"reality_private_key,omitempty"` + RealityPublicKey string `json:"reality_public_key,omitempty"` + RealityShortId string `json:"reality_short_id,omitempty"` + Transport string `json:"transport,omitempty"` + Host string `json:"host,omitempty"` + Path string `json:"path,omitempty"` + ServiceName string `json:"service_name,omitempty"` + Cipher string `json:"cipher,omitempty"` + ServerKey string `json:"server_key,omitempty"` + Flow string `json:"flow,omitempty"` + HopPorts string `json:"hop_ports,omitempty"` + HopInterval int `json:"hop_interval,omitempty"` + ObfsPassword string `json:"obfs_password,omitempty"` + DisableSNI bool `json:"disable_sni,omitempty"` + ReduceRtt bool `json:"reduce_rtt,omitempty"` + UDPRelayMode string `json:"udp_relay_mode,omitempty"` + CongestionController string `json:"congestion_controller,omitempty"` + Multiplex string `json:"multiplex,omitempty"` // mux, eg: off/low/medium/high + PaddingScheme string `json:"padding_scheme,omitempty"` // padding scheme + UpMbps int `json:"up_mbps,omitempty"` // upload speed limit + DownMbps int `json:"down_mbps,omitempty"` // download speed limit + Obfs string `json:"obfs,omitempty"` // obfs, 'none', 'http', 'tls' + ObfsHost string `json:"obfs_host,omitempty"` // obfs host + ObfsPath string `json:"obfs_path,omitempty"` // obfs path + XhttpMode string `json:"xhttp_mode,omitempty"` // xhttp mode + XhttpExtra string `json:"xhttp_extra,omitempty"` // xhttp extra path + Encryption string `json:"encryption,omitempty"` // encryption,'none', 'mlkem768x25519plus' + EncryptionMode string `json:"encryption_mode,omitempty"` // encryption mode,'native', 'xorpub', 'random' + EncryptionRtt string `json:"encryption_rtt,omitempty"` // encryption rtt,'0rtt', '1rtt' + EncryptionTicket string `json:"encryption_ticket,omitempty"` // encryption ticket + EncryptionServerPadding string `json:"encryption_server_padding,omitempty"` // encryption server padding + EncryptionPrivateKey string `json:"encryption_private_key,omitempty"` // encryption private key + EncryptionClientPadding string `json:"encryption_client_padding,omitempty"` // encryption client padding + EncryptionPassword string `json:"encryption_password,omitempty"` // encryption password + Ratio float64 `json:"ratio,omitempty"` // Traffic ratio, default is 1 + CertMode string `json:"cert_mode,omitempty"` // Certificate mode, `none`|`http`|`dns`|`self` + CertDNSProvider string `json:"cert_dns_provider,omitempty"` // DNS provider for certificate + CertDNSEnv string `json:"cert_dns_env,omitempty"` // Environment for DNS provider +} + type PubilcRegisterConfig struct { StopRegister bool `json:"stop_register"` EnableIpRegisterLimit bool `json:"enable_ip_register_limit"` @@ -1297,7 +1529,7 @@ type PubilcVerifyCodeConfig struct { type PurchaseOrderRequest struct { SubscribeId int64 `json:"subscribe_id"` - Quantity int64 `json:"quantity"` + Quantity int64 `json:"quantity" validate:"required,gt=0"` Payment int64 `json:"payment,omitempty"` Coupon string `json:"coupon,omitempty"` } @@ -1327,6 +1559,10 @@ type QueryDocumentListResponse struct { List []Document `json:"list"` } +type QueryNodeTagResponse struct { + Tags []string `json:"tags"` +} + type QueryOrderDetailRequest struct { OrderNo string `form:"order_no" validate:"required"` } @@ -1363,11 +1599,64 @@ type QueryPurchaseOrderResponse struct { Token string `json:"token,omitempty"` } +type QueryQuotaTaskListRequest struct { + Page int `form:"page"` + Size int `form:"size"` + Status *uint8 `form:"status,omitempty"` +} + +type QueryQuotaTaskListResponse struct { + Total int64 `json:"total"` + List []QuotaTask `json:"list"` +} + +type QueryQuotaTaskPreCountRequest struct { + Subscribers []int64 `json:"subscribers"` + IsActive *bool `json:"is_active"` + StartTime int64 `json:"start_time"` + EndTime int64 `json:"end_time"` +} + +type QueryQuotaTaskPreCountResponse struct { + Count int64 `json:"count"` +} + +type QueryQuotaTaskStatusRequest struct { + Id int64 `json:"id"` +} + +type QueryQuotaTaskStatusResponse struct { + Status uint8 `json:"status"` + Current int64 `json:"current"` + Total int64 `json:"total"` + Errors string `json:"errors"` +} + +type QueryServerConfigRequest struct { + ServerID int64 `path:"server_id"` + SecretKey string `form:"secret_key"` + Protocols []string `form:"protocols,omitempty"` +} + +type QueryServerConfigResponse struct { + TrafficReportThreshold int64 `json:"traffic_report_threshold"` + IPStrategy string `json:"ip_strategy"` + DNS []NodeDNS `json:"dns"` + Block []string `json:"block"` + Outbound []NodeOutbound `json:"outbound"` + Protocols []Protocol `json:"protocols"` + Total int64 `json:"total"` +} + type QuerySubscribeGroupListResponse struct { List []SubscribeGroup `json:"list"` Total int64 `json:"total"` } +type QuerySubscribeListRequest struct { + Language string `form:"language"` +} + type QuerySubscribeListResponse struct { List []Subscribe `json:"list"` Total int64 `json:"total"` @@ -1389,8 +1678,8 @@ type QueryUserAffiliateListResponse struct { } type QueryUserBalanceLogListResponse struct { - List []UserBalanceLog `json:"list"` - Total int64 `json:"total"` + List []BalanceLog `json:"list"` + Total int64 `json:"total"` } type QueryUserCommissionLogListRequest struct { @@ -1408,8 +1697,23 @@ type QueryUserSubscribeListResponse struct { Total int64 `json:"total"` } -type QueryUserSubscribeResp struct { - Data []UserSubscribeData `json:"data"` +type QuotaTask struct { + Id int64 `json:"id"` + Subscribers []int64 `json:"subscribers"` + IsActive *bool `json:"is_active"` + StartTime int64 `json:"start_time"` + EndTime int64 `json:"end_time"` + ResetTraffic bool `json:"reset_traffic"` + Days uint64 `json:"days"` + GiftType uint8 `json:"gift_type"` + GiftValue uint64 `json:"gift_value"` + Objects []int64 `json:"objects"` // UserSubscribe IDs + Status uint8 `json:"status"` + Total int64 `json:"total"` + Current int64 `json:"current"` + Errors string `json:"errors"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` } type RechargeOrderRequest struct { @@ -1432,6 +1736,15 @@ type RegisterConfig struct { IpRegisterLimitDuration int64 `json:"ip_register_limit_duration"` } +type RegisterLog struct { + UserId int64 `json:"user_id"` + AuthMethod string `json:"auth_method"` + Identifier string `json:"identifier"` + RegisterIP string `json:"register_ip"` + UserAgent string `json:"user_agent"` + Timestamp int64 `json:"timestamp"` +} + type RenewalOrderRequest struct { UserSubscribeID int64 `json:"user_subscribe_id"` Quantity int64 `json:"quantity"` @@ -1444,12 +1757,34 @@ type RenewalOrderResponse struct { } type ResetPasswordRequest struct { - Email string `json:"email" validate:"required"` - Password string `json:"password" validate:"required"` - Code string `json:"code,optional"` - IP string `header:"X-Original-Forwarded-For"` - UserAgent string `header:"User-Agent"` - CfToken string `json:"cf_token,optional"` + Identifier string `json:"identifier"` + Email string `json:"email" validate:"required"` + Password string `json:"password" validate:"required"` + Code string `json:"code,optional"` + IP string `header:"X-Original-Forwarded-For"` + UserAgent string `header:"User-Agent"` + LoginType string `header:"Login-Type"` + CfToken string `json:"cf_token,optional"` +} + +type ResetSortRequest struct { + Sort []SortItem `json:"sort"` +} + +type ResetSubscribeLog struct { + Type uint16 `json:"type"` + UserId int64 `json:"user_id"` + UserSubscribeId int64 `json:"user_subscribe_id"` + OrderNo string `json:"order_no,omitempty"` + Timestamp int64 `json:"timestamp"` +} + +type ResetSubscribeTrafficLog struct { + Id int64 `json:"id"` + Type uint16 `json:"type"` + UserSubscribeId int64 `json:"user_subscribe_id"` + OrderNo string `json:"order_no,omitempty"` + Timestamp int64 `json:"timestamp"` } type ResetTrafficOrderRequest struct { @@ -1499,24 +1834,17 @@ type SendSmsCodeRequest struct { } type Server struct { - Id int64 `json:"id"` - Tags []string `json:"tags"` - Country string `json:"country"` - City string `json:"city"` - Name string `json:"name"` - ServerAddr string `json:"server_addr"` - RelayMode string `json:"relay_mode"` - RelayNode []NodeRelay `json:"relay_node"` - SpeedLimit int `json:"speed_limit"` - TrafficRatio float32 `json:"traffic_ratio"` - GroupId int64 `json:"group_id"` - Protocol string `json:"protocol"` - Config interface{} `json:"config"` - Enable *bool `json:"enable"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` - Status *NodeStatus `json:"status"` - Sort int64 `json:"sort"` + Id int64 `json:"id"` + Name string `json:"name"` + Country string `json:"country"` + City string `json:"city"` + Address string `json:"address"` + Sort int `json:"sort"` + Protocols []Protocol `json:"protocols"` + LastReportedAt int64 `json:"last_reported_at"` + Status ServerStatus `json:"status"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` } type ServerBasic struct { @@ -1538,6 +1866,20 @@ type ServerGroup struct { UpdatedAt int64 `json:"updated_at"` } +type ServerOnlineIP struct { + IP string `json:"ip"` + Protocol string `json:"protocol"` +} + +type ServerOnlineUser struct { + IP []ServerOnlineIP `json:"ip"` + UserId int64 `json:"user_id"` + Subscribe string `json:"subscribe"` + SubscribeId int64 `json:"subscribe_id"` + Traffic int64 `json:"traffic"` + ExpiredAt int64 `json:"expired_at"` +} + type ServerPushStatusRequest struct { ServerCommon Cpu float64 `json:"cpu"` @@ -1555,15 +1897,26 @@ type ServerRuleGroup struct { Id int64 `json:"id"` Icon string `json:"icon"` Name string `json:"name" validate:"required"` + Type string `json:"type"` Tags []string `json:"tags"` Rules string `json:"rules"` Enable bool `json:"enable"` + Default bool `json:"default"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` } +type ServerStatus struct { + Cpu float64 `json:"cpu"` + Mem float64 `json:"mem"` + Disk float64 `json:"disk"` + Protocol string `json:"protocol"` + Online []ServerOnlineUser `json:"online"` + Status string `json:"status"` +} + type ServerTotalDataResponse struct { - OnlineUserIPs int64 `json:"online_user_ips"` + OnlineUsers int64 `json:"online_users"` OnlineServers int64 `json:"online_servers"` OfflineServers int64 `json:"offline_servers"` TodayUpload int64 `json:"today_upload"` @@ -1584,6 +1937,15 @@ type ServerTrafficData struct { Download int64 `json:"download"` } +type ServerTrafficLog struct { + ServerId int64 `json:"server_id"` // Server ID + Upload int64 `json:"upload"` // Upload traffic in bytes + Download int64 `json:"download"` // Download traffic in bytes + Total int64 `json:"total"` // Total traffic in bytes (Upload + Download) + Date string `json:"date"` // Date in YYYY-MM-DD format + Details bool `json:"details"` // Whether to show detailed traffic +} + type ServerUser struct { Id int64 `json:"id"` UUID string `json:"uuid"` @@ -1627,6 +1989,10 @@ type SortItem struct { Sort int64 `json:"sort" validate:"required"` } +type StopBatchSendEmailTaskRequest struct { + Id int64 `json:"id"` +} + type StripePayment struct { Method string `json:"method"` ClientSecret string `json:"client_secret"` @@ -1636,6 +2002,7 @@ type StripePayment struct { type Subscribe struct { Id int64 `json:"id"` Name string `json:"name"` + Language string `json:"language"` Description string `json:"description"` UnitPrice int64 `json:"unit_price"` UnitTime string `json:"unit_time"` @@ -1646,9 +2013,8 @@ type Subscribe struct { SpeedLimit int64 `json:"speed_limit"` DeviceLimit int64 `json:"device_limit"` Quota int64 `json:"quota"` - GroupId int64 `json:"group_id"` - ServerGroup []int64 `json:"server_group"` - Server []int64 `json:"server"` + Nodes []int64 `json:"nodes"` + NodeTags []string `json:"node_tags"` Show bool `json:"show"` Sell bool `json:"sell"` Sort int64 `json:"sort"` @@ -1660,11 +2026,38 @@ type Subscribe struct { UpdatedAt int64 `json:"updated_at"` } +type SubscribeApplication struct { + Id int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Icon string `json:"icon,omitempty"` + Scheme string `json:"scheme,omitempty"` + UserAgent string `json:"user_agent"` + IsDefault bool `json:"is_default"` + SubscribeTemplate string `json:"template"` + OutputFormat string `json:"output_format"` + DownloadLink DownloadLink `json:"download_link,omitempty"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} + +type SubscribeClient struct { + Id int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Icon string `json:"icon,omitempty"` + Scheme string `json:"scheme,omitempty"` + IsDefault bool `json:"is_default"` + DownloadLink DownloadLink `json:"download_link,omitempty"` +} + type SubscribeConfig struct { SingleModel bool `json:"single_model"` SubscribePath string `json:"subscribe_path"` SubscribeDomain string `json:"subscribe_domain"` PanDomain bool `json:"pan_domain"` + UserAgentLimit bool `json:"user_agent_limit"` + UserAgentList string `json:"user_agent_list"` } type SubscribeDiscount struct { @@ -1685,6 +2078,15 @@ type SubscribeItem struct { Sold int64 `json:"sold"` } +type SubscribeLog struct { + UserId int64 `json:"user_id"` + Token string `json:"token"` + UserAgent string `json:"user_agent"` + ClientIP string `json:"client_ip"` + UserSubscribeId int64 `json:"user_subscribe_id"` + Timestamp int64 `json:"timestamp"` +} + type SubscribeSortRequest struct { Sort []SortItem `json:"sort"` } @@ -1710,30 +2112,39 @@ type TelephoneCheckUserResponse struct { } type TelephoneLoginRequest struct { + Identifier string `json:"identifier"` Telephone string `json:"telephone" validate:"required"` TelephoneCode string `json:"telephone_code"` TelephoneAreaCode string `json:"telephone_area_code" validate:"required"` Password string `json:"password"` IP string `header:"X-Original-Forwarded-For"` + UserAgent string `header:"User-Agent"` + LoginType string `header:"Login-Type"` CfToken string `json:"cf_token,optional"` } type TelephoneRegisterRequest struct { + Identifier string `json:"identifier"` Telephone string `json:"telephone" validate:"required"` TelephoneAreaCode string `json:"telephone_area_code" validate:"required"` Password string `json:"password" validate:"required"` Invite string `json:"invite,optional"` Code string `json:"code,optional"` IP string `header:"X-Original-Forwarded-For"` + UserAgent string `header:"User-Agent"` + LoginType string `header:"Login-Type,optional"` CfToken string `json:"cf_token,optional"` } type TelephoneResetPasswordRequest struct { + Identifier string `json:"identifier"` Telephone string `json:"telephone" validate:"required"` TelephoneAreaCode string `json:"telephone_area_code" validate:"required"` Password string `json:"password" validate:"required"` Code string `json:"code,optional"` IP string `header:"X-Original-Forwarded-For"` + UserAgent string `header:"User-Agent"` + LoginType string `header:"Login-Type,optional"` CfToken string `json:"cf_token,optional"` } @@ -1767,6 +2178,11 @@ type TimePeriod struct { Multiplier float32 `json:"multiplier"` } +type ToggleNodeStatusRequest struct { + Id int64 `json:"id"` + Enable *bool `json:"enable"` +} + type TosConfig struct { TosContent string `json:"tos_content"` } @@ -1781,6 +2197,16 @@ type TrafficLog struct { Timestamp int64 `json:"timestamp"` } +type TrafficLogDetails struct { + Id int64 `json:"id"` + ServerId int64 `json:"server_id"` + UserId int64 `json:"user_id"` + SubscribeId int64 `json:"subscribe_id"` + Download int64 `json:"download"` + Upload int64 `json:"upload"` + Timestamp int64 `json:"timestamp"` +} + type TransportConfig struct { Path string `json:"path"` Host string `json:"host"` @@ -1805,8 +2231,16 @@ type TrojanProtocol struct { } type Tuic struct { - Port int `json:"port" validate:"required"` - SecurityConfig SecurityConfig `json:"security_config"` + Port int `json:"port" validate:"required"` + DisableSNI bool `json:"disable_sni"` + ReduceRtt bool `json:"reduce_rtt"` + UDPRelayMode string `json:"udp_relay_mode"` + CongestionController string `json:"congestion_controller"` + SecurityConfig SecurityConfig `json:"security_config"` +} + +type UnbindDeviceRequest struct { + Id int64 `json:"id" validate:"required"` } type UnbindOAuthRequest struct { @@ -1843,25 +2277,6 @@ type UpdateAnnouncementRequest struct { Popup *bool `json:"popup"` } -type UpdateApplicationRequest struct { - Id int64 `json:"id" validate:"required"` - Icon string `json:"icon"` - Name string `json:"name"` - Description string `json:"description"` - SubscribeType string `json:"subscribe_type"` - Platform ApplicationPlatform `json:"platform"` -} - -type UpdateApplicationVersionRequest struct { - Id int64 `json:"id" validate:"required"` - Url string `json:"url"` - Version string `json:"version" validate:"required"` - Description string `json:"description"` - Platform string `json:"platform" validate:"required,oneof=windows mac linux android ios harmony"` - IsDefault bool `json:"is_default"` - ApplicationId int64 `json:"application_id" validate:"required"` -} - type UpdateAuthMethodConfigRequest struct { Id int64 `json:"id"` Method string `json:"method"` @@ -1902,28 +2317,15 @@ type UpdateDocumentRequest struct { Show *bool `json:"show"` } -type UpdateNodeGroupRequest struct { - Id int64 `json:"id" validate:"required"` - Name string `json:"name" validate:"required"` - Description string `json:"description"` -} - type UpdateNodeRequest struct { - Id int64 `json:"id" validate:"required"` - Tags []string `json:"tags"` - Country string `json:"country"` - City string `json:"city"` - Name string `json:"name" validate:"required"` - ServerAddr string `json:"server_addr" validate:"required"` - RelayMode string `json:"relay_mode"` - RelayNode []NodeRelay `json:"relay_node"` - SpeedLimit int `json:"speed_limit"` - TrafficRatio float32 `json:"traffic_ratio"` - GroupId int64 `json:"group_id"` - Protocol string `json:"protocol" validate:"required"` - Config interface{} `json:"config" validate:"required"` - Enable *bool `json:"enable"` - Sort int64 `json:"sort"` + Id int64 `json:"id"` + Name string `json:"name"` + Tags []string `json:"tags,omitempty"` + Port uint16 `json:"port"` + Address string `json:"address"` + ServerId int64 `json:"server_id"` + Protocol string `json:"protocol"` + Enabled *bool `json:"enabled"` } type UpdateOrderStatusRequest struct { @@ -1933,11 +2335,6 @@ type UpdateOrderStatusRequest struct { TradeNo string `json:"trade_no,omitempty"` } -type UpdatePasswordRequeset struct { - Password string `json:"password"` - NewPassword string `json:"new_password"` -} - type UpdatePaymentMethodRequest struct { Id int64 `json:"id" validate:"required"` Name string `json:"name" validate:"required"` @@ -1952,13 +2349,27 @@ type UpdatePaymentMethodRequest struct { Enable *bool `json:"enable" validate:"required"` } -type UpdateRuleGroupRequest struct { - Id int64 `json:"id" validate:"required"` - Icon string `json:"icon"` - Name string `json:"name" validate:"required"` - Tags []string `json:"tags"` - Rules string `json:"rules"` - Enable bool `json:"enable"` +type UpdateServerRequest struct { + Id int64 `json:"id"` + Name string `json:"name"` + Country string `json:"country,omitempty"` + City string `json:"city,omitempty"` + Address string `json:"address"` + Sort int `json:"sort,omitempty"` + Protocols []Protocol `json:"protocols"` +} + +type UpdateSubscribeApplicationRequest struct { + Id int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Icon string `json:"icon,omitempty"` + Scheme string `json:"scheme,omitempty"` + UserAgent string `json:"user_agent"` + IsDefault bool `json:"is_default"` + SubscribeTemplate string `json:"template"` + OutputFormat string `json:"output_format"` + DownloadLink DownloadLink `json:"download_link,omitempty"` } type UpdateSubscribeGroupRequest struct { @@ -1970,6 +2381,7 @@ type UpdateSubscribeGroupRequest struct { type UpdateSubscribeRequest struct { Id int64 `json:"id" validate:"required"` Name string `json:"name" validate:"required"` + Language string `json:"language"` Description string `json:"description"` UnitPrice int64 `json:"unit_price"` UnitTime string `json:"unit_time"` @@ -1980,9 +2392,8 @@ type UpdateSubscribeRequest struct { SpeedLimit int64 `json:"speed_limit"` DeviceLimit int64 `json:"device_limit"` Quota int64 `json:"quota"` - GroupId int64 `json:"group_id"` - ServerGroup []int64 `json:"server_group"` - Server []int64 `json:"server"` + Nodes []int64 `json:"nodes"` + NodeTags []string `json:"node_tags"` Show *bool `json:"show"` Sell *bool `json:"sell"` Sort int64 `json:"sort"` @@ -2004,17 +2415,19 @@ type UpdateUserAuthMethodRequest struct { } type UpdateUserBasiceInfoRequest struct { - UserId int64 `json:"user_id" validate:"required"` - Password string `json:"password"` - Avatar string `json:"avatar"` - Balance int64 `json:"balance"` - Commission int64 `json:"commission"` - GiftAmount int64 `json:"gift_amount"` - Telegram int64 `json:"telegram"` - ReferCode string `json:"refer_code"` - RefererId int64 `json:"referer_id"` - Enable bool `json:"enable"` - IsAdmin bool `json:"is_admin"` + UserId int64 `json:"user_id" validate:"required"` + Password string `json:"password"` + Avatar string `json:"avatar"` + Balance int64 `json:"balance"` + Commission int64 `json:"commission"` + ReferralPercentage uint8 `json:"referral_percentage"` + OnlyFirstPurchase bool `json:"only_first_purchase"` + GiftAmount int64 `json:"gift_amount"` + Telegram int64 `json:"telegram"` + ReferCode string `json:"refer_code"` + RefererId int64 `json:"referer_id"` + Enable bool `json:"enable"` + IsAdmin bool `json:"is_admin"` } type UpdateUserNotifyRequest struct { @@ -2055,6 +2468,8 @@ type User struct { Avatar string `json:"avatar"` Balance int64 `json:"balance"` Commission int64 `json:"commission"` + ReferralPercentage uint8 `json:"referral_percentage"` + OnlyFirstPurchase bool `json:"only_first_purchase"` GiftAmount int64 `json:"gift_amount"` Telegram int64 `json:"telegram"` ReferCode string `json:"refer_code"` @@ -2086,16 +2501,6 @@ type UserAuthMethod struct { Verified bool `json:"verified"` } -type UserBalanceLog struct { - Id int64 `json:"id"` - UserId int64 `json:"user_id"` - Amount int64 `json:"amount"` - Type uint8 `json:"type"` - OrderId int64 `json:"order_id"` - Balance int64 `json:"balance"` - CreatedAt int64 `json:"created_at"` -} - type UserDevice struct { Id int64 `json:"id"` Ip string `json:"ip"` @@ -2107,44 +2512,35 @@ type UserDevice struct { UpdatedAt int64 `json:"updated_at"` } -type UserInfoResponse struct { - Id int64 `json:"id"` - Balance int64 `json:"balance"` - Email string `json:"email"` - RefererId int64 `json:"referer_id"` - ReferCode string `json:"refer_code"` - Avatar string `json:"avatar"` - AreaCode string `json:"area_code"` - Telephone string `json:"telephone"` - Devices []UserDevice `json:"devices"` - AuthMethods []UserAuthMethod `json:"auth_methods"` -} - type UserLoginLog struct { Id int64 `json:"id"` UserId int64 `json:"user_id"` LoginIP string `json:"login_ip"` UserAgent string `json:"user_agent"` Success bool `json:"success"` - CreatedAt int64 `json:"created_at"` + Timestamp int64 `json:"timestamp"` } type UserLoginRequest struct { - Email string `json:"email" validate:"required"` - Password string `json:"password" validate:"required"` - IP string `header:"X-Original-Forwarded-For"` - UserAgent string `header:"User-Agent"` - CfToken string `json:"cf_token,optional"` + Identifier string `json:"identifier"` + Email string `json:"email" validate:"required"` + Password string `json:"password" validate:"required"` + IP string `header:"X-Original-Forwarded-For"` + UserAgent string `header:"User-Agent"` + LoginType string `header:"Login-Type"` + CfToken string `json:"cf_token,optional"` } type UserRegisterRequest struct { - Email string `json:"email" validate:"required"` - Password string `json:"password" validate:"required"` - Invite string `json:"invite,optional"` - Code string `json:"code,optional"` - IP string `header:"X-Original-Forwarded-For"` - UserAgent string `header:"User-Agent"` - CfToken string `json:"cf_token,optional"` + Identifier string `json:"identifier"` + Email string `json:"email" validate:"required"` + Password string `json:"password" validate:"required"` + Invite string `json:"invite,optional"` + Code string `json:"code,optional"` + IP string `header:"X-Original-Forwarded-For"` + UserAgent string `header:"User-Agent"` + LoginType string `header:"Login-Type"` + CfToken string `json:"cf_token,optional"` } type UserStatistics struct { @@ -2180,11 +2576,6 @@ type UserSubscribe struct { UpdatedAt int64 `json:"updated_at"` } -type UserSubscribeData struct { - SubscribeId int64 `json:"subscribe_id"` - UserSubscribeId int64 `json:"user_subscribe_id"` -} - type UserSubscribeDetail struct { Id int64 `json:"id"` UserId int64 `json:"user_id"` @@ -2211,15 +2602,17 @@ type UserSubscribeLog struct { Token string `json:"token"` IP string `json:"ip"` UserAgent string `json:"user_agent"` - CreatedAt int64 `json:"created_at"` + Timestamp int64 `json:"timestamp"` } -type UserSubscribeResetPeriodRequest struct { - UserSubscribeId int64 `json:"user_subscribe_id"` -} - -type UserSubscribeResetPeriodResponse struct { - Status bool `json:"status"` +type UserSubscribeTrafficLog struct { + SubscribeId int64 `json:"subscribe_id"` // Subscribe ID + UserId int64 `json:"user_id"` // User ID + Upload int64 `json:"upload"` // Upload traffic in bytes + Download int64 `json:"download"` // Download traffic in bytes + Total int64 `json:"total"` // Total traffic in bytes (Upload + Download) + Date string `json:"date"` // Date in YYYY-MM-DD format + Details bool `json:"details"` // Whether to show detailed traffic } type UserTraffic struct { @@ -2260,6 +2653,10 @@ type VerifyEmailRequest struct { Code string `json:"code" validate:"required"` } +type VersionResponse struct { + Version string `json:"version"` +} + type Vless struct { Port int `json:"port" validate:"required"` Flow string `json:"flow" validate:"required"` @@ -2295,9 +2692,3 @@ type VmessProtocol struct { Network string `json:"network"` Transport string `json:"transport"` } - -type WeeklyStat struct { - Day int `json:"day"` - DayName string `json:"day_name"` - Hours float64 `json:"hours"` -} diff --git a/pkg/adapter/adapter.go b/pkg/adapter/adapter.go deleted file mode 100644 index aaec788..0000000 --- a/pkg/adapter/adapter.go +++ /dev/null @@ -1,71 +0,0 @@ -package adapter - -import ( - "github.com/perfect-panel/ppanel-server/internal/model/server" - "github.com/perfect-panel/ppanel-server/pkg/adapter/clash" - "github.com/perfect-panel/ppanel-server/pkg/adapter/general" - "github.com/perfect-panel/ppanel-server/pkg/adapter/loon" - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" - "github.com/perfect-panel/ppanel-server/pkg/adapter/quantumultx" - "github.com/perfect-panel/ppanel-server/pkg/adapter/shadowrocket" - "github.com/perfect-panel/ppanel-server/pkg/adapter/singbox" - "github.com/perfect-panel/ppanel-server/pkg/adapter/surfboard" -) - -type Adapter struct { - proxy.Adapter -} - -func NewAdapter(nodes []*server.Server, rules []*server.RuleGroup) *Adapter { - // 转换服务器列表 - proxies := adapterProxies(nodes) - // 生成代理组 - proxyGroup, region := generateProxyGroup(proxies) - // 转换规则组 - g, r := adapterRules(rules) - // 加入兜底节点 - for i, group := range g { - if len(group.Proxies) == 0 { - g[i].Proxies = append([]string{"DIRECT"}, region...) - } - } - // 合并代理组 - proxyGroup = append(proxyGroup, g...) - return &Adapter{ - Adapter: proxy.Adapter{ - Proxies: proxies, - Group: proxyGroup, - Rules: r, - Region: region, - }, - } -} - -func (m *Adapter) BuildClash(uuid string) ([]byte, error) { - client := clash.NewClash(m.Adapter) - return client.Build(uuid) -} - -func (m *Adapter) BuildGeneral(uuid string) []byte { - return general.GenerateBase64General(m.Proxies, uuid) -} - -func (m *Adapter) BuildLoon(uuid string) []byte { - return loon.BuildLoon(m.Proxies, uuid) -} - -func (m *Adapter) BuildQuantumultX(uuid string) string { - return quantumultx.BuildQuantumultX(m.Proxies, uuid) -} - -func (m *Adapter) BuildSingbox(uuid string) ([]byte, error) { - return singbox.BuildSingbox(m.Adapter, uuid) -} - -func (m *Adapter) BuildShadowrocket(uuid string, userInfo shadowrocket.UserInfo) []byte { - return shadowrocket.BuildShadowrocket(m.Proxies, uuid, userInfo) -} - -func (m *Adapter) BuildSurfboard(siteName string, user surfboard.UserInfo) []byte { - return surfboard.BuildSurfboard(m.Adapter, siteName, user) -} diff --git a/pkg/adapter/adapter_test.go b/pkg/adapter/adapter_test.go deleted file mode 100644 index 25c5a64..0000000 --- a/pkg/adapter/adapter_test.go +++ /dev/null @@ -1,138 +0,0 @@ -package adapter - -import ( - "encoding/json" - "fmt" - "testing" - "time" - - "github.com/perfect-panel/ppanel-server/internal/model/server" - "github.com/perfect-panel/ppanel-server/pkg/adapter/surfboard" -) - -func createTestServer() []*server.Server { - c := server.Shadowsocks{ - Method: "aes-256-gcm", - Port: 10301, - ServerKey: "", - } - data, _ := json.Marshal(c) - - relays := creatRelayNode() - relay, _ := json.Marshal(relays) - enable := true - // 创建一个测试用的服务器列表 - return []*server.Server{ - { - Id: 1, - Name: "Test Server 1", - Tags: "", - Country: "CN", - City: "", - Latitude: "", - Longitude: "", - ServerAddr: "test1.example.com", - RelayMode: "random", - RelayNode: string(relay), - SpeedLimit: 0, - TrafficRatio: 0, - GroupId: 0, - Protocol: "shadowsocks", - Config: string(data), - Enable: &enable, - Sort: 0, - }, - } -} -func creatRelayNode() []*server.NodeRelay { - var nodes []*server.NodeRelay - for i := 0; i < 10; i++ { - port := 10301 + i - c := server.NodeRelay{ - Host: fmt.Sprintf("192.168.1.%d", i), - Port: port, - Prefix: fmt.Sprintf("relay-%d", i), - } - nodes = append(nodes, &c) - } - return nodes -} - -func TestNewAdapter(t *testing.T) { - nodes := createTestServer() - - rules := []*server.RuleGroup{ - { - Name: "Test Rule Group 1", - Tags: "", - Rules: "DOMAIN-SUFFIX,example.com,Test Rule Group 1", - }, - } - - adapter := NewAdapter(nodes, rules) - bytes, err := adapter.BuildClash("some-uuid") - if err != nil { - t.Errorf("Failed to build adapter: %v", err) - return - } - t.Logf("Adapter built successfully: %s", string(bytes)) -} - -func TestAdapter_BuildSingbox(t *testing.T) { - nodes := createTestServer() - - rules := []*server.RuleGroup{ - { - Name: "Test Rule Group 1", - Tags: "", - Rules: "DOMAIN-SUFFIX,example.com,Test Rule Group 1", - }, - } - - adapter := NewAdapter(nodes, rules) - bytes, err := adapter.BuildSingbox("some-uuid") - if err != nil { - t.Errorf("Failed to build adapter: %v", err) - return - } - var pretty map[string]interface{} - _ = json.Unmarshal(bytes, &pretty) - - if pretty == nil { - t.Errorf("Failed to parse Singbox config") - return - } - - prettyStr, err := json.MarshalIndent(pretty, "", " ") - if err != nil { - t.Errorf("Failed to format Singbox config: %v", err) - return - } - t.Logf("Adapter built successfully: \n %s", string(prettyStr)) -} - -func TestAdapter_BuildSurfboard(t *testing.T) { - nodes := createTestServer() - rules := []*server.RuleGroup{ - { - Name: "Test Rule Group 1", - Tags: "", - Rules: "DOMAIN-SUFFIX,example.com,Test Rule Group 1", - }, - } - adapter := NewAdapter(nodes, rules) - user := surfboard.UserInfo{ - UUID: "some-uuid", - Upload: 200, - Download: 13012, - TotalTraffic: 1024000, - ExpiredDate: time.Now().Add(24 * time.Hour), - SubscribeURL: "", - } - bytes := adapter.BuildSurfboard("test-site", user) - if bytes == nil { - t.Errorf("Failed to build adapter") - return - } - t.Logf("Adapter built successfully: %s", string(bytes)) -} diff --git a/pkg/adapter/clash/clash.go b/pkg/adapter/clash/clash.go deleted file mode 100644 index 52bcf61..0000000 --- a/pkg/adapter/clash/clash.go +++ /dev/null @@ -1,68 +0,0 @@ -package clash - -import ( - "fmt" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "gopkg.in/yaml.v3" -) - -type Clash struct { - proxy.Adapter -} - -func NewClash(adapter proxy.Adapter) *Clash { - return &Clash{ - Adapter: adapter, - } -} - -func (c *Clash) Build(uuid string) ([]byte, error) { - var proxies []Proxy - for _, v := range c.Proxies { - p, err := c.parseProxy(v, uuid) - if err != nil { - logger.Errorf("Failed to parse proxy for %s: %s", v.Name, err.Error()) - continue - } - proxies = append(proxies, *p) - } - var rawConfig RawConfig - if err := yaml.Unmarshal([]byte(DefaultTemplate), &rawConfig); err != nil { - return nil, fmt.Errorf("failed to unmarshal template: %w", err) - } - rawConfig.Proxies = proxies - // generate proxy groups - var groups []ProxyGroup - for _, group := range c.Group { - groups = append(groups, ProxyGroup{ - Name: group.Name, - Type: string(group.Type), - Proxies: group.Proxies, - Url: group.URL, - Interval: group.Interval, - }) - } - rawConfig.ProxyGroups = groups - rawConfig.Rules = append(c.Rules, "# 最终规则", "MATCH,手动选择") - return yaml.Marshal(&rawConfig) -} - -func (c *Clash) parseProxy(p proxy.Proxy, uuid string) (*Proxy, error) { - parseFuncs := map[string]func(proxy.Proxy, string) (*Proxy, error){ - "shadowsocks": parseShadowsocks, - "trojan": parseTrojan, - "vless": parseVless, - "vmess": parseVmess, - "hysteria2": parseHysteria2, - "tuic": parseTuic, - } - - if parseFunc, exists := parseFuncs[p.Protocol]; exists { - return parseFunc(p, uuid) - } - - logger.Errorw("Unknown protocol", logger.Field("protocol", p.Protocol), logger.Field("server", p.Name)) - return nil, fmt.Errorf("unknown protocol: %s", p.Protocol) -} diff --git a/pkg/adapter/clash/clash_test.go b/pkg/adapter/clash/clash_test.go deleted file mode 100644 index 1ab8156..0000000 --- a/pkg/adapter/clash/clash_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package clash - -import ( - "testing" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" - "github.com/stretchr/testify/assert" -) - -func TestClash_Build(t *testing.T) { - adapter := proxy.Adapter{ - Proxies: []proxy.Proxy{ - { - Name: "test-proxy", - Protocol: "shadowsocks", - Server: "1.2.3.4", - Port: 8388, - Option: proxy.Shadowsocks{ - Method: "aes-256-gcm", - }, - }, - }, - Group: []proxy.Group{ - { - Name: "test-group", - Type: "select", - Proxies: []string{"test-proxy"}, - }, - }, - Rules: []string{ - "DOMAIN-SUFFIX,example.com,DIRECT", - "GEOIP,CN,DIRECT", - "MATCH,DIRECT", - }, - } - clash := NewClash(adapter) - result, err := clash.Build("test-uuid") - assert.NoError(t, err) - assert.NotNil(t, result) - -} diff --git a/pkg/adapter/clash/default.go b/pkg/adapter/clash/default.go deleted file mode 100644 index 4766053..0000000 --- a/pkg/adapter/clash/default.go +++ /dev/null @@ -1,35 +0,0 @@ -package clash - -const DefaultTemplate = ` -mixed-port: 7890 -allow-lan: true -bind-address: "*" -mode: rule -log-level: info -external-controller: 127.0.0.1:9090 -global-client-fingerprint: chrome -unified-delay: true -geox-url: - mmdb: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb" -dns: - enable: true - ipv6: true - enhanced-mode: fake-ip - fake-ip-range: 198.18.0.1/16 - use-hosts: true - default-nameserver: - - 120.53.53.53 - - 1.12.12.12 - nameserver: - - https://120.53.53.53/dns-query#skip-cert-verify=true - - tls://1.12.12.12#skip-cert-verify=true - proxy-server-nameserver: - - https://120.53.53.53/dns-query#skip-cert-verify=true - - tls://1.12.12.12#skip-cert-verify=true - -proxies: - -proxy-groups: - -rules: -` diff --git a/pkg/adapter/clash/model.go b/pkg/adapter/clash/model.go deleted file mode 100644 index 9f9925d..0000000 --- a/pkg/adapter/clash/model.go +++ /dev/null @@ -1,131 +0,0 @@ -package clash - -type RawConfig struct { - Port int `yaml:"port" json:"port"` - SocksPort int `yaml:"socks-port" json:"socks-port"` - RedirPort int `yaml:"redir-port" json:"redir-port"` - TProxyPort int `yaml:"tproxy-port" json:"tproxy-port"` - MixedPort int `yaml:"mixed-port" json:"mixed-port"` - AllowLan bool `yaml:"allow-lan" json:"allow-lan"` - Mode string `yaml:"mode" json:"mode"` - LogLevel string `yaml:"log-level" json:"log-level"` - ExternalController string `yaml:"external-controller" json:"external-controller"` - Secret string `yaml:"secret" json:"secret"` - Proxies []Proxy `yaml:"proxies" json:"proxies"` - ProxyGroups []ProxyGroup `yaml:"proxy-groups" json:"proxy-groups"` - Rules []string `yaml:"rules" json:"rule"` -} - -type Proxy struct { - // 基础数据 - Name string `yaml:"name"` - Type string `yaml:"type"` - Server string `yaml:"server"` - Port int `yaml:"port,omitempty"` - // Shadowsocks - Password string `yaml:"password,omitempty"` - Cipher string `yaml:"cipher,omitempty"` - UDP bool `yaml:"udp,omitempty"` - Plugin string `yaml:"plugin,omitempty"` - PluginOpts map[string]any `yaml:"plugin-opts,omitempty"` - UDPOverTCP bool `yaml:"udp-over-tcp,omitempty"` - UDPOverTCPVersion int `yaml:"udp-over-tcp-version,omitempty"` - ClientFingerprint string `yaml:"client-fingerprint,omitempty"` - // Vmess - UUID string `yaml:"uuid,omitempty"` - AlterID *int `yaml:"alterId,omitempty"` - Network string `yaml:"network,omitempty"` - TLS bool `yaml:"tls,omitempty"` - ALPN []string `yaml:"alpn,omitempty"` - SkipCertVerify bool `yaml:"skip-cert-verify,omitempty"` - Fingerprint string `yaml:"fingerprint,omitempty"` - ServerName string `yaml:"servername,omitempty"` - RealityOpts RealityOptions `yaml:"reality-opts,omitempty"` - HTTPOpts HTTPOptions `yaml:"http-opts,omitempty"` - HTTP2Opts HTTP2Options `yaml:"h2-opts,omitempty"` - GrpcOpts GrpcOptions `yaml:"grpc-opts,omitempty"` - WSOpts WSOptions `yaml:"ws-opts,omitempty"` - PacketAddr bool `yaml:"packet-addr,omitempty"` - XUDP bool `yaml:"xudp,omitempty"` - PacketEncoding string `yaml:"packet-encoding,omitempty"` - GlobalPadding bool `yaml:"global-padding,omitempty"` - AuthenticatedLength bool `yaml:"authenticated-length,omitempty"` - // Vless - Flow string `yaml:"flow,omitempty"` - WSPath string `yaml:"ws-path,omitempty"` - WSHeaders map[string]string `yaml:"ws-headers,omitempty"` - // Trojan - SNI string `yaml:"sni,omitempty"` - SSOpts TrojanSSOption `yaml:"ss-opts,omitempty"` - // Hysteria2 - Ports string `yaml:"ports,omitempty"` - HopInterval int `yaml:"hop-interval,omitempty"` - Up string `yaml:"up,omitempty"` - Down string `yaml:"down,omitempty"` - Obfs string `yaml:"obfs,omitempty"` - ObfsPassword string `yaml:"obfs-password,omitempty"` - CustomCA string `yaml:"ca,omitempty"` - CustomCAString string `yaml:"ca-str,omitempty"` - CWND int `yaml:"cwnd,omitempty"` - UdpMTU int `yaml:"udp-mtu,omitempty"` - // Tuic - Token string `yaml:"token,omitempty"` - Ip string `yaml:"ip,omitempty"` - HeartbeatInterval int `yaml:"heartbeat-interval,omitempty"` - ReduceRtt bool `yaml:"reduce-rtt,omitempty"` - RequestTimeout int `yaml:"request-timeout,omitempty"` - UdpRelayMode string `yaml:"udp-relay-mode,omitempty"` - CongestionController string `yaml:"congestion-controller,omitempty"` - DisableSni bool `yaml:"disable-sni,omitempty"` - MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size,omitempty"` - FastOpen bool `yaml:"fast-open,omitempty"` - MaxOpenStreams int `yaml:"max-open-streams,omitempty"` - ReceiveWindowConn int `yaml:"recv-window-conn,omitempty"` - ReceiveWindow int `yaml:"recv-window,omitempty"` - DisableMTUDiscovery bool `yaml:"disable-mtu-discovery,omitempty"` - MaxDatagramFrameSize int `yaml:"max-datagram-frame-size,omitempty"` - UDPOverStream bool `yaml:"udp-over-stream,omitempty"` - UDPOverStreamVersion int `yaml:"udp-over-stream-version,omitempty"` -} -type ProxyGroup struct { - Name string `yaml:"name"` - Type string `yaml:"type"` - Proxies []string `yaml:"proxies"` - Url string `yaml:"url,omitempty"` - Interval int `yaml:"interval,omitempty"` -} - -type TrojanSSOption struct { - Enabled bool `yaml:"enabled,omitempty"` - Method string `yaml:"method,omitempty"` - Password string `yaml:"password,omitempty"` -} - -type RealityOptions struct { - PublicKey string `yaml:"public-key"` - ShortID string `yaml:"short-id"` -} - -type HTTPOptions struct { - Method string `yaml:"method,omitempty"` - Path []string `yaml:"path,omitempty"` - Headers map[string][]string `yaml:"headers,omitempty"` -} - -type HTTP2Options struct { - Host []string `yaml:"host,omitempty"` - Path string `yaml:"path,omitempty"` -} - -type GrpcOptions struct { - GrpcServiceName string `yaml:"grpc-service-name,omitempty"` -} - -type WSOptions struct { - Path string `yaml:"path,omitempty"` - Headers map[string]string `yaml:"headers,omitempty"` - MaxEarlyData int `yaml:"max-early-data,omitempty"` - EarlyDataHeaderName string `yaml:"early-data-header-name,omitempty"` - V2rayHttpUpgrade bool `yaml:"v2ray-http-upgrade,omitempty"` - V2rayHttpUpgradeFastOpen bool `yaml:"v2ray-http-upgrade-fast-open,omitempty"` -} diff --git a/pkg/adapter/clash/parse.go b/pkg/adapter/clash/parse.go deleted file mode 100644 index a52c331..0000000 --- a/pkg/adapter/clash/parse.go +++ /dev/null @@ -1,165 +0,0 @@ -package clash - -import ( - "fmt" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func parseShadowsocks(s proxy.Proxy, uuid string) (*Proxy, error) { - config, ok := s.Option.(proxy.Shadowsocks) - if !ok { - return nil, fmt.Errorf("invalid type for Shadowsocks") - } - p := &Proxy{ - Name: s.Name, - Type: "ss", - Server: s.Server, - Port: s.Port, - Cipher: config.Method, - Password: uuid, - UDP: true, - } - - return p, nil -} - -func parseTrojan(data proxy.Proxy, password string) (*Proxy, error) { - trojan, ok := data.Option.(proxy.Trojan) - if !ok { - return nil, fmt.Errorf("invalid type for Trojan") - } - p := &Proxy{ - Name: data.Name, - Type: "trojan", - Server: data.Server, - Port: data.Port, - Password: password, - SNI: trojan.SecurityConfig.SNI, - SkipCertVerify: trojan.SecurityConfig.AllowInsecure, - } - setTransportOptions(p, trojan.Transport, trojan.TransportConfig) - return p, nil -} - -func parseVless(data proxy.Proxy, uuid string) (*Proxy, error) { - vless, ok := data.Option.(proxy.Vless) - if !ok { - return nil, fmt.Errorf("invalid type for Vless") - } - p := &Proxy{ - Name: data.Name, - Type: "vless", - Server: data.Server, - Port: data.Port, - UUID: uuid, - Flow: vless.Flow, - } - setSecurityOptions(p, vless.Security, vless.SecurityConfig) - clashTransport(p, vless.Transport, vless.TransportConfig) - return p, nil -} - -func parseVmess(data proxy.Proxy, uuid string) (*Proxy, error) { - vmess, ok := data.Option.(proxy.Vmess) - if !ok { - return nil, fmt.Errorf("invalid type for Vmess") - } - alterID := 0 - p := &Proxy{ - Name: data.Name, - Type: "vmess", - Server: data.Server, - Port: data.Port, - UUID: uuid, - AlterID: &alterID, - Cipher: "auto", - } - setSecurityOptions(p, vmess.Security, vmess.SecurityConfig) - clashTransport(p, vmess.Transport, vmess.TransportConfig) - return p, nil -} - -func parseHysteria2(data proxy.Proxy, uuid string) (*Proxy, error) { - hysteria2, ok := data.Option.(proxy.Hysteria2) - if !ok { - return nil, fmt.Errorf("invalid type for Hysteria2") - } - p := &Proxy{ - Name: data.Name, - Type: "hysteria2", - Server: data.Server, - Port: data.Port, - Ports: hysteria2.HopPorts, - Password: uuid, - HeartbeatInterval: hysteria2.HopInterval, - SkipCertVerify: hysteria2.SecurityConfig.AllowInsecure, - SNI: hysteria2.SecurityConfig.SNI, - } - if hysteria2.ObfsPassword != "" { - p.Obfs = "salamander" - p.ObfsPassword = hysteria2.ObfsPassword - } - - return p, nil -} - -func parseTuic(data proxy.Proxy, uuid string) (*Proxy, error) { - tuic, ok := data.Option.(proxy.Tuic) - if !ok { - return nil, fmt.Errorf("invalid type for Tuic") - } - p := &Proxy{ - Name: data.Name, - Type: "tuic", - Server: data.Server, - Port: data.Port, - UUID: uuid, - Password: uuid, - SNI: tuic.SecurityConfig.SNI, - SkipCertVerify: tuic.SecurityConfig.AllowInsecure, - } - - return p, nil -} - -func setSecurityOptions(p *Proxy, security string, config proxy.SecurityConfig) { - switch security { - case "tls": - p.TLS = true - p.ServerName = config.SNI - p.ClientFingerprint = config.Fingerprint - p.SkipCertVerify = config.AllowInsecure - case "reality": - p.TLS = true - p.ServerName = config.SNI - p.ClientFingerprint = config.Fingerprint - p.RealityOpts = RealityOptions{ - PublicKey: config.RealityPublicKey, - ShortID: config.RealityShortId, - } - p.SkipCertVerify = config.AllowInsecure - default: - p.TLS = false - } -} - -func setTransportOptions(p *Proxy, transport string, config proxy.TransportConfig) { - switch transport { - case "websocket": - p.Network = "ws" - p.WSOpts = WSOptions{ - Path: config.Path, - Headers: map[string]string{ - "Host": config.Host, - }, - } - case "grpc": - p.Network = "grpc" - p.GrpcOpts = GrpcOptions{ - GrpcServiceName: config.ServiceName, - } - default: - p.Network = "tcp" - } -} diff --git a/pkg/adapter/clash/tool.go b/pkg/adapter/clash/tool.go deleted file mode 100644 index e4c989e..0000000 --- a/pkg/adapter/clash/tool.go +++ /dev/null @@ -1,33 +0,0 @@ -package clash - -import "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" - -func clashTransport(c *Proxy, transportType string, transportConfig proxy.TransportConfig) { - - switch transportType { - case "websocket", "httpupgrade": - if transportType == "websocket" { - c.Network = "ws" - } else { - c.Network = transportType - } - c.WSOpts = WSOptions{ - Path: transportConfig.Path, - Headers: map[string]string{}, - } - if transportConfig.Host != "" { - c.WSOpts.Headers["host"] = transportConfig.Host - } - if transportType == "httpupgrade" { - c.WSOpts.V2rayHttpUpgrade = true - } - case "grpc": - c.Network = "grpc" - c.GrpcOpts = GrpcOptions{ - GrpcServiceName: transportConfig.ServiceName, - } - case "tcp": - c.Network = "tcp" - } - -} diff --git a/pkg/adapter/general/uri.go b/pkg/adapter/general/uri.go deleted file mode 100644 index a5871fb..0000000 --- a/pkg/adapter/general/uri.go +++ /dev/null @@ -1,245 +0,0 @@ -package general - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "net" - "net/url" - "strconv" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -type v2rayShareLink struct { - Ps string `json:"ps"` - Add string `json:"add"` - Port string `json:"port"` - ID string `json:"id"` - Aid string `json:"aid"` - Net string `json:"net"` - Type string `json:"type"` - Host string `json:"host"` - SNI string `json:"sni"` - Path string `json:"path"` - TLS string `json:"tls"` - Flow string `json:"flow,omitempty"` - Alpn string `json:"alpn,omitempty"` - AllowInsecure bool `json:"allowInsecure"` - Fingerprint string `json:"fp,omitempty"` - PublicKey string `json:"pbk,omitempty"` - ShortId string `json:"sid,omitempty"` - SpiderX string `json:"spx,omitempty"` - V string `json:"v"` -} - -// GenerateBase64General will output node URLs split by '\n' and then encode into base64 -func GenerateBase64General(data []proxy.Proxy, uuid string) []byte { - var links []string - for _, v := range data { - p := buildProxy(v, uuid) - if p == "" { - continue - } - links = append(links, p) - } - var rsp []byte - rsp = base64.RawStdEncoding.AppendEncode(rsp, []byte(strings.Join(links, "\n"))) - return rsp -} - -func buildProxy(data proxy.Proxy, uuid string) string { - switch data.Protocol { - case "shadowsocks": - return ShadowsocksUri(data, uuid) - case "vmess": - return VmessUri(data, uuid) - case "vless": - return VlessUri(data, uuid) - case "trojan": - return TrojanUri(data, uuid) - case "hysteria2": - return Hysteria2Uri(data, uuid) - case "tuic": - return TuicUri(data, uuid) - default: - return "" - } -} - -func ShadowsocksUri(data proxy.Proxy, uuid string) string { - ss := data.Option.(proxy.Shadowsocks) - // sip002 - u := &url.URL{ - Scheme: "ss", - // 还没有写 2022 的 - User: url.User(strings.TrimSuffix(base64.URLEncoding.EncodeToString([]byte(ss.Method+":"+uuid)), "=")), - Host: net.JoinHostPort(data.Server, strconv.Itoa(data.Port)), - Fragment: data.Name, - } - return u.String() -} - -func VmessUri(data proxy.Proxy, uuid string) string { - vmess := data.Option.(proxy.Vmess) - - transport := vmess.TransportConfig - - securityConfig := vmess.SecurityConfig - - var s = v2rayShareLink{ - V: "2", - Add: data.Server, - Port: fmt.Sprint(data.Port), - ID: uuid, - Aid: "0", - Net: vmess.Transport, - // Type: "?", - Host: transport.Host, - Path: transport.Path, - } - - if vmess.Security == "tls" { - s.TLS = "tls" - s.SNI = securityConfig.SNI - s.AllowInsecure = securityConfig.AllowInsecure - s.Fingerprint = securityConfig.Fingerprint - } - b, _ := json.Marshal(s) - return "vmess://" + strings.TrimSuffix(base64.StdEncoding.EncodeToString(b), "=") -} - -func VlessUri(data proxy.Proxy, uuid string) string { - vless := data.Option.(proxy.Vless) - transportConfig := vless.TransportConfig - securityConfig := vless.SecurityConfig - - var query = make(url.Values) - setQuery(&query, "flow", vless.Flow) - setQuery(&query, "type", vless.Transport) - setQuery(&query, "security", vless.Security) - - switch vless.Transport { - case "ws", "http", "httpupgrade": - setQuery(&query, "path", transportConfig.Path) - setQuery(&query, "host", transportConfig.Host) - case "grpc": - setQuery(&query, "serviceName", transportConfig.ServiceName) - case "meek": - setQuery(&query, "url", transportConfig.Host) - } - - setQuery(&query, "sni", securityConfig.SNI) - setQuery(&query, "fp", securityConfig.Fingerprint) - setQuery(&query, "pbk", securityConfig.RealityPublicKey) - setQuery(&query, "sid", securityConfig.RealityShortId) - - u := url.URL{ - Scheme: "vless", - User: url.User(uuid), - Host: net.JoinHostPort(data.Server, fmt.Sprint(data.Port)), - RawQuery: query.Encode(), - Fragment: data.Name, - } - return u.String() -} - -func TrojanUri(data proxy.Proxy, uuid string) string { - trojan := data.Option.(proxy.Trojan) - transportConfig := trojan.TransportConfig - securityConfig := trojan.SecurityConfig - - var query = make(url.Values) - setQuery(&query, "type", trojan.Transport) - setQuery(&query, "security", trojan.Security) - - switch trojan.Transport { - case "ws", "http", "httpupgrade": - setQuery(&query, "path", transportConfig.Path) - setQuery(&query, "host", transportConfig.Host) - case "grpc": - setQuery(&query, "serviceName", transportConfig.ServiceName) - case "meek": - setQuery(&query, "url", transportConfig.Host) - } - - setQuery(&query, "sni", securityConfig.SNI) - setQuery(&query, "fp", securityConfig.Fingerprint) - setQuery(&query, "pbk", securityConfig.RealityPublicKey) - setQuery(&query, "sid", securityConfig.RealityShortId) - - if securityConfig.AllowInsecure { - setQuery(&query, "allowInsecure", "1") - } - - u := &url.URL{ - Scheme: "trojan", - User: url.User(uuid), - Host: net.JoinHostPort(data.Server, strconv.Itoa(data.Port)), - RawQuery: query.Encode(), - Fragment: data.Name, - } - return u.String() -} - -func Hysteria2Uri(data proxy.Proxy, uuid string) string { - hysteria2 := data.Option.(proxy.Hysteria2) - - var query = make(url.Values) - - setQuery(&query, "sni", hysteria2.SecurityConfig.SNI) - - if hysteria2.SecurityConfig.AllowInsecure { - setQuery(&query, "insecure", "1") - } - - if hp := strings.TrimSpace(hysteria2.HopPorts); hp != "" { - setQuery(&query, "mport", hp) - } - - if hysteria2.ObfsPassword != "" { - setQuery(&query, "obfs", "salamander") - setQuery(&query, "obfs-password", hysteria2.ObfsPassword) - } - - u := &url.URL{ - Scheme: "hysteria2", - User: url.User(uuid), - Host: net.JoinHostPort(data.Server, strconv.Itoa(data.Port)), - RawQuery: query.Encode(), - Fragment: data.Name, - } - return u.String() -} - -func TuicUri(data proxy.Proxy, uuid string) string { - tuic := data.Option.(proxy.Tuic) - var query = make(url.Values) - - setQuery(&query, "congestion_control", "bbr") - - if tuic.SecurityConfig.SNI == "" { - setQuery(&query, "sni", tuic.SecurityConfig.SNI) - } else { - setQuery(&query, "disable_sni", "1") - } - if tuic.SecurityConfig.AllowInsecure { - setQuery(&query, "allow_insecure", "1") - } - - u := &url.URL{ - Scheme: "tuic", - User: url.User(uuid + ":" + uuid), - Host: net.JoinHostPort(data.Server, strconv.Itoa(data.Port)), - RawQuery: query.Encode(), - Fragment: data.Name, - } - return u.String() -} - -func setQuery(q *url.Values, k, v string) { - if v != "" { - q.Set(k, v) - } -} diff --git a/pkg/adapter/general/uri_test.go b/pkg/adapter/general/uri_test.go deleted file mode 100644 index a33ef10..0000000 --- a/pkg/adapter/general/uri_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package general - -import ( - "testing" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func createServer() proxy.Proxy { - return proxy.Proxy{ - Name: "Meta", - Server: "127.0.0.1", - Port: 13092, - Protocol: "shadowsocks", - Option: proxy.Shadowsocks{ - Method: "aes-256-gcm", - ServerKey: "", - }, - } -} - -func TestGenerateBase64General(t *testing.T) { - s := createServer() - p := buildProxy(s, "935b33c7-e128-49f2-816b-71070469cac2") - t.Log(p) -} diff --git a/pkg/adapter/loon/build.go b/pkg/adapter/loon/build.go deleted file mode 100644 index 3256385..0000000 --- a/pkg/adapter/loon/build.go +++ /dev/null @@ -1,27 +0,0 @@ -package loon - -import ( - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func BuildLoon(servers []proxy.Proxy, uuid string) []byte { - uri := "" - for _, s := range servers { - switch s.Protocol { - case "vmess": - uri += buildVMess(s, uuid) - case "shadowsocks": - uri += buildShadowsocks(s, uuid) - case "trojan": - uri += buildTrojan(s, uuid) - case "vless": - uri += buildVless(s, uuid) - case "hysteria2": - uri += buildHysteria2(s, uuid) - default: - continue - } - } - - return []byte(uri) -} diff --git a/pkg/adapter/loon/hysteria2.go b/pkg/adapter/loon/hysteria2.go deleted file mode 100644 index 5b1547f..0000000 --- a/pkg/adapter/loon/hysteria2.go +++ /dev/null @@ -1,34 +0,0 @@ -package loon - -import ( - "fmt" - "strconv" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func buildHysteria2(data proxy.Proxy, password string) string { - hysteria2 := data.Option.(proxy.Hysteria2) - - configs := []string{ - fmt.Sprintf("%s=Hysteria2", data.Name), - data.Server, - strconv.Itoa(data.Port), - password, - "udp=true", - } - if hysteria2.ObfsPassword != "" { - configs = append(configs, "obfs=salamander", fmt.Sprintf("salamander-password=%s", hysteria2.ObfsPassword)) - } - if hysteria2.SecurityConfig.SNI != "" { - configs = append(configs, fmt.Sprintf("sni=%s", hysteria2.SecurityConfig.SNI)) - if hysteria2.SecurityConfig.AllowInsecure { - configs = append(configs, "skip-cert-verify=true") - } else { - configs = append(configs, "skip-cert-verify=false") - } - } - uri := strings.Join(configs, ",") - return uri + "\r\n" -} diff --git a/pkg/adapter/loon/loon_test.go b/pkg/adapter/loon/loon_test.go deleted file mode 100644 index 281cd5a..0000000 --- a/pkg/adapter/loon/loon_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package loon - -import ( - "testing" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func createSS() proxy.Proxy { - return proxy.Proxy{ - Name: "Shadowsocks", - Server: "127.0.0.1", - Port: 10301, - Protocol: "shadowsocks", - Option: proxy.Shadowsocks{ - Method: "aes-256-gcm", - ServerKey: "", - }, - } - -} - -func TestBuildSS(t *testing.T) { - s := createSS() - - password := "f0d0237d-193a-4cf5-99dd-b02207beaea6" - uri := buildShadowsocks(s, password) - t.Log(uri) -} diff --git a/pkg/adapter/loon/shadowsocks.go b/pkg/adapter/loon/shadowsocks.go deleted file mode 100644 index ffd7494..0000000 --- a/pkg/adapter/loon/shadowsocks.go +++ /dev/null @@ -1,49 +0,0 @@ -package loon - -import ( - "fmt" - "strconv" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" -) - -func buildShadowsocks(data proxy.Proxy, password string) string { - shadowsocks := data.Option.(proxy.Shadowsocks) - // If the method is 2022-blake3-chacha20-poly1305, it means that the server is a relay server - if shadowsocks.Method == "2022-blake3-chacha20-poly1305" { - return "" - } - - if strings.Contains(shadowsocks.Method, "2022") { - serverKey, userKey := generateShadowsocks2022Password(shadowsocks, password) - password = fmt.Sprintf("%s:%s", serverKey, userKey) - } - - configs := []string{ - fmt.Sprintf("%s=Shadowsocks", data.Name), - data.Server, - strconv.Itoa(data.Port), - shadowsocks.Method, - password, - "fast-open=false", - "udp=true", - } - uri := strings.Join(configs, ",") - return uri + "\r\n" -} - -func generateShadowsocks2022Password(ss proxy.Shadowsocks, password string) (string, string) { - // server key - var serverKey string - if ss.Method == "2022-blake3-aes-128-gcm" { - serverKey = tool.GenerateCipher(ss.ServerKey, 16) - password = uuidx.UUIDToBase64(password, 16) - } else { - serverKey = tool.GenerateCipher(ss.ServerKey, 32) - password = uuidx.UUIDToBase64(password, 32) - } - return serverKey, password -} diff --git a/pkg/adapter/loon/trojan.go b/pkg/adapter/loon/trojan.go deleted file mode 100644 index 3017d91..0000000 --- a/pkg/adapter/loon/trojan.go +++ /dev/null @@ -1,44 +0,0 @@ -package loon - -import ( - "fmt" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func buildTrojan(data proxy.Proxy, password string) string { - trojan := data.Option.(proxy.Trojan) - - configs := []string{ - fmt.Sprintf("%s=trojan", data.Name), - data.Server, - fmt.Sprintf("%d", data.Port), - "auto", - password, - "fast-open=false", - "udp=true", - } - - if trojan.SecurityConfig.SNI != "" { - configs = append(configs, fmt.Sprintf("sni=%s", trojan.SecurityConfig.SNI)) - } - if trojan.SecurityConfig.AllowInsecure { - configs = append(configs, "skip-cert-verify=true") - } else { - configs = append(configs, "skip-cert-verify=false") - } - - if trojan.Transport == "websocket" { - configs = append(configs, "transport=ws") - if trojan.TransportConfig.Path != "" { - configs = append(configs, fmt.Sprintf("path=%s", trojan.TransportConfig.Path)) - } - if trojan.TransportConfig.Host != "" { - configs = append(configs, fmt.Sprintf("host=%s", trojan.TransportConfig.Host)) - } - } - - uri := strings.Join(configs, ",") - return uri + "\r\n" -} diff --git a/pkg/adapter/loon/vless.go b/pkg/adapter/loon/vless.go deleted file mode 100644 index f14bbcb..0000000 --- a/pkg/adapter/loon/vless.go +++ /dev/null @@ -1,62 +0,0 @@ -package loon - -import ( - "fmt" - "strconv" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" - - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -func buildVless(data proxy.Proxy, password string) string { - vless := data.Option.(proxy.Vless) - // If flow is not empty, it means that the server is a relay server - if vless.Flow != "" { - return "" - } - - configs := []string{ - fmt.Sprintf("%s=vless", data.Name), - data.Server, - strconv.Itoa(data.Port), - "auto", - password, - "fast-open=false", - "udp=true", - "alterId=0", - } - - switch vless.Transport { - case "tcp": - configs = append(configs, "transport=tcp") - case "websocket": - configs = append(configs, "transport=ws") - if vless.TransportConfig.Path != "" { - configs = append(configs, fmt.Sprintf("path=%s", vless.TransportConfig.Path)) - } - if vless.TransportConfig.Host != "" { - configs = append(configs, fmt.Sprintf("host=%s", vless.TransportConfig.Host)) - } - default: - logger.Info("Loon Unknown transport type: ", logger.Field("transport", vless.Transport)) - return "" - } - - if vless.Security == "tls" { - configs = append(configs, "over-tls=true", fmt.Sprintf("tls-name=%s", vless.SecurityConfig.SNI)) - if vless.SecurityConfig.AllowInsecure { - configs = append(configs, "skip-cert-verify=true") - } else { - configs = append(configs, "skip-cert-verify=false") - } - } else if vless.Security == "reality" { - // Loon does not support reality security - logger.Info("Loon Unknown security type: ", logger.Field("security", vless.Security)) - return "" - } - - uri := strings.Join(configs, ",") - return uri + "\r\n" -} diff --git a/pkg/adapter/loon/vmess.go b/pkg/adapter/loon/vmess.go deleted file mode 100644 index 5d693b1..0000000 --- a/pkg/adapter/loon/vmess.go +++ /dev/null @@ -1,53 +0,0 @@ -package loon - -import ( - "fmt" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -func buildVMess(data proxy.Proxy, password string) string { - vmess := data.Option.(proxy.Vmess) - - configs := []string{ - fmt.Sprintf("%s=vmess", data.Name), - data.Server, - fmt.Sprintf("%d", data.Port), - "auto", - password, - "fast-open=false", - "udp=true", - "alterId=0", - } - - switch vmess.Transport { - case "tcp": - configs = append(configs, "transport=tcp") - case "websocket": - configs = append(configs, "transport=ws") - if vmess.TransportConfig.Path != "" { - configs = append(configs, fmt.Sprintf("path=%s", vmess.TransportConfig.Path)) - } - if vmess.TransportConfig.Host != "" { - configs = append(configs, fmt.Sprintf("host=%s", vmess.TransportConfig.Host)) - } - default: - logger.Info("Loon Unknown transport type: ", logger.Field("transport", vmess.Transport)) - return "" - } - - if vmess.Security == "tls" { - configs = append(configs, "over-tls=true", fmt.Sprintf("tls-name=%s", vmess.SecurityConfig.SNI)) - if vmess.SecurityConfig.AllowInsecure { - configs = append(configs, "skip-cert-verify=true") - } else { - configs = append(configs, "skip-cert-verify=false") - } - - } - - uri := strings.Join(configs, ",") - return uri + "\r\n" -} diff --git a/pkg/adapter/proxy/proxy.go b/pkg/adapter/proxy/proxy.go deleted file mode 100644 index d2cf603..0000000 --- a/pkg/adapter/proxy/proxy.go +++ /dev/null @@ -1,114 +0,0 @@ -package proxy - -// Adapter represents a proxy adapter -type Adapter struct { - Proxies []Proxy - Group []Group - Rules []string - Region []string -} - -// Proxy represents a proxy server -type Proxy struct { - Name string - Server string - Port int - Protocol string - Country string - Option any -} - -// Group represents a group of proxies -type Group struct { - Name string - Type GroupType - Proxies []string - URL string - Interval int -} - -type GroupType string - -const ( - GroupTypeSelect GroupType = "select" - GroupTypeURLTest GroupType = "url-test" - GroupTypeFallback GroupType = "fallback" -) - -// Shadowsocks represents a Shadowsocks proxy configuration -type Shadowsocks struct { - Port int `json:"port"` - Method string `json:"method"` - ServerKey string `json:"server_key"` -} - -// Vless represents a Vless proxy configuration -type Vless struct { - Port int `json:"port"` - Flow string `json:"flow"` - Transport string `json:"transport"` - TransportConfig TransportConfig `json:"transport_config"` - Security string `json:"security"` - SecurityConfig SecurityConfig `json:"security_config"` -} - -// Vmess represents a Vmess proxy configuration -type Vmess struct { - Port int `json:"port"` - Flow string `json:"flow"` - Transport string `json:"transport"` - TransportConfig TransportConfig `json:"transport_config"` - Security string `json:"security"` - SecurityConfig SecurityConfig `json:"security_config"` -} - -// Trojan represents a Trojan proxy configuration -type Trojan struct { - Port int `json:"port"` - Flow string `json:"flow"` - Transport string `json:"transport"` - TransportConfig TransportConfig `json:"transport_config"` - Security string `json:"security"` - SecurityConfig SecurityConfig `json:"security_config"` -} - -// Hysteria2 represents a Hysteria2 proxy configuration -type Hysteria2 struct { - Port int `json:"port"` - HopPorts string `json:"hop_ports"` - HopInterval int `json:"hop_interval"` - ObfsPassword string `json:"obfs_password"` - SecurityConfig SecurityConfig `json:"security_config"` -} - -// Tuic represents a Tuic proxy configuration -type Tuic struct { - Port int `json:"port"` - SecurityConfig SecurityConfig `json:"security_config"` -} - -// TransportConfig represents the transport configuration for a proxy -type TransportConfig struct { - Path string `json:"path,omitempty"` // ws/httpupgrade - Host string `json:"host,omitempty"` - ServiceName string `json:"service_name"` // grpc -} - -// SecurityConfig represents the security configuration for a proxy -type SecurityConfig struct { - SNI string `json:"sni"` - AllowInsecure bool `json:"allow_insecure"` - Fingerprint string `json:"fingerprint"` - RealityServerAddr string `json:"reality_server_addr"` - RealityServerPort int `json:"reality_server_port"` - RealityPrivateKey string `json:"reality_private_key"` - RealityPublicKey string `json:"reality_public_key"` - RealityShortId string `json:"reality_short_id"` -} - -// Relay represents a relay configuration -type Relay struct { - RelayHost string - DispatchMode string - Prefix string -} diff --git a/pkg/adapter/quantumultx/build.go b/pkg/adapter/quantumultx/build.go deleted file mode 100644 index a5b02cd..0000000 --- a/pkg/adapter/quantumultx/build.go +++ /dev/null @@ -1,22 +0,0 @@ -package quantumultx - -import ( - "encoding/base64" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func BuildQuantumultX(servers []proxy.Proxy, uuid string) string { - var uri string - for _, s := range servers { - switch s.Protocol { - case "vmess": - uri += buildVmess(s, uuid) - case "shadowsocks": - uri += buildShadowsocks(s, uuid) - case "trojan": - uri += buildTrojan(s, uuid) - } - } - return base64.StdEncoding.EncodeToString([]byte(uri)) -} diff --git a/pkg/adapter/quantumultx/quantumux_test.go b/pkg/adapter/quantumultx/quantumux_test.go deleted file mode 100644 index 79524bb..0000000 --- a/pkg/adapter/quantumultx/quantumux_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package quantumultx - -import ( - "testing" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func createVMess() proxy.Proxy { - - return proxy.Proxy{ - Name: "Vmess", - Server: "test.xxxx.com", - Port: 13002, - Protocol: "vmess", - Option: proxy.Vmess{ - Port: 13002, - Transport: "websocket", - TransportConfig: proxy.TransportConfig{ - Path: "/ws", - Host: "test.xx.com", - }, - Security: "none", - }, - } -} - -func createSS() proxy.Proxy { - return proxy.Proxy{ - Name: "Shadowsocks", - Server: "test.xxxx.com", - Port: 10301, - Protocol: "shadowsocks", - Option: proxy.Shadowsocks{ - Port: 10301, - Method: "aes-256-gcm", - ServerKey: "123456", - }, - } -} - -func createTrojan() proxy.Proxy { - - return proxy.Proxy{ - Name: "Trojan", - Server: "test.xxxx.com", - Port: 13002, - Protocol: "trojan", - Option: proxy.Trojan{ - Port: 13002, - Transport: "websocket", - TransportConfig: proxy.TransportConfig{ - Path: "/ws", - Host: "baidu.com", - }, - SecurityConfig: proxy.SecurityConfig{ - SNI: "baidu.com", - AllowInsecure: true, - }, - }, - } -} -func TestVmess(t *testing.T) { - s := createVMess() - vmess := buildVmess(s, "uuid") - t.Log(vmess) - // output: - // vmess=127.0.0.1:13002,method=chacha20-poly1305,password=uuid,fast-open=true,udp-relay=true,tag=Vmess,tls-verification=true,obfs-uri=/ws,obfs-host=baidu.com -} - -func TestShadowsocks(t *testing.T) { - s := createSS() - shadowsocks := buildShadowsocks(s, "uuid") - t.Log(shadowsocks) - // output: - // shadowsocks=127.0.0.1:10301,method=aes-256-gcm,password=uuid,fast-open=true,udp-relay=true,tag=Shadowsocks -} - -func TestTrojan(t *testing.T) { - s := createTrojan() - trojan := buildTrojan(s, "password") - t.Log(trojan) - // output: - // trojan=192.168.0.1:13002,password=password,fast-open=true,udp-relay=true,tag=Trojan,obfs=wss,obfs-uri=ws,obfs-host=baidu.com -} - -func TestBuildQuantumultX(t *testing.T) { - var servers []proxy.Proxy - uri := BuildQuantumultX(servers, "uuid") - t.Log(uri) - - // output: - // c2hhZG93c29ja3M9MTI3LjAuMC4xOjEwMzAxLG1ldGhvZD1hZXMtMjU2LWdjbSxwYXNzd29yZD11dWlkLGZhc3Qtb3Blbj10cnVlLHVkcC1yZWxheT10cnVlLHRhZz1TaGFkb3dzb2Nrcw0KdHJvamFuPTE5Mi4xNjguMC4xOjEzMDAyLHBhc3N3b3JkPXV1aWQsZmFzdC1vcGVuPXRydWUsdWRwLXJlbGF5PXRydWUsdGFnPVRyb2phbixvYmZzPXdzcyxvYmZzLXVyaT13cyxvYmZzLWhvc3Q9YmFpZHUuY29tDQp2bWVzcz0xMjcuMC4wLjE6MTMwMDIsbWV0aG9kPWNoYWNoYTIwLXBvbHkxMzA1LHBhc3N3b3JkPXV1aWQsZmFzdC1vcGVuPXRydWUsdWRwLXJlbGF5PXRydWUsdGFnPVZtZXNzLHRscy12ZXJpZmljYXRpb249dHJ1ZSxvYmZzLXVyaT0vd3Msb2Jmcy1ob3N0PWJhaWR1LmNvbQ0K -} diff --git a/pkg/adapter/quantumultx/shadowsocks.go b/pkg/adapter/quantumultx/shadowsocks.go deleted file mode 100644 index a03703c..0000000 --- a/pkg/adapter/quantumultx/shadowsocks.go +++ /dev/null @@ -1,23 +0,0 @@ -package quantumultx - -import ( - "fmt" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func buildShadowsocks(data proxy.Proxy, uuid string) string { - ss := data.Option.(proxy.Shadowsocks) - addr := fmt.Sprintf("%s:%d", data.Server, data.Port) - - config := []string{ - addr, - fmt.Sprintf("method=%s", ss.Method), - fmt.Sprintf("password=%s", uuid), - "fast-open=true", - "udp-relay=true", - fmt.Sprintf("tag=%s", data.Name), - } - return strings.Join(config, ",") + "\r\n" -} diff --git a/pkg/adapter/quantumultx/trojan.go b/pkg/adapter/quantumultx/trojan.go deleted file mode 100644 index 0ebcd96..0000000 --- a/pkg/adapter/quantumultx/trojan.go +++ /dev/null @@ -1,39 +0,0 @@ -package quantumultx - -import ( - "fmt" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -// 生成 Trojan 配置 -func buildTrojan(data proxy.Proxy, password string) string { - trojan := data.Option.(proxy.Trojan) - - addr := fmt.Sprintf("trojan=%s:%d", data.Server, data.Port) - config := []string{ - addr, - fmt.Sprintf("password=%s", password), - "fast-open=true", - "udp-relay=true", - fmt.Sprintf("tag=%s", data.Name), - } - - if trojan.Transport == "websocket" { - config = append(config, "obfs=wss") - if trojan.TransportConfig.Path != "" { - config = append(config, fmt.Sprintf("obfs-uri=%s", trojan.TransportConfig.Path)) - } - if trojan.TransportConfig.Host != "" { - config = append(config, fmt.Sprintf("obfs-host=%s", trojan.TransportConfig.Host)) - } - } else { - config = append(config, "over-tls=true") - if trojan.SecurityConfig.SNI != "" { - config = append(config, fmt.Sprintf("tls-host=%s", trojan.SecurityConfig.SNI)) - } - } - - return strings.Join(config, ",") + "\r\n" -} diff --git a/pkg/adapter/quantumultx/vmess.go b/pkg/adapter/quantumultx/vmess.go deleted file mode 100644 index d3cf13e..0000000 --- a/pkg/adapter/quantumultx/vmess.go +++ /dev/null @@ -1,45 +0,0 @@ -package quantumultx - -import ( - "fmt" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func buildVmess(data proxy.Proxy, uuid string) string { - - vmess := data.Option.(proxy.Vmess) - addr := fmt.Sprintf("vmess=%s:%d", data.Server, data.Port) - var host string - uriConfig := []string{ - addr, - "method=chacha20-poly1305", - fmt.Sprintf("password=%s", uuid), - "fast-open=true", - "udp-relay=true", - fmt.Sprintf("tag=%s", data.Name), - } - if vmess.Security == "tls" { - if vmess.Transport == "tcp" { - uriConfig = append(uriConfig, "obfs=over-tls") - } - if vmess.SecurityConfig.AllowInsecure { - uriConfig = append(uriConfig, "tls-verification=true") - } else { - uriConfig = append(uriConfig, "tls-verification=false") - } - if vmess.SecurityConfig.SNI != "" { - host = vmess.SecurityConfig.SNI - } - } - - if vmess.Transport == "websocket" { - uriConfig = append(uriConfig, fmt.Sprintf("obfs-uri=%s", vmess.TransportConfig.Path)) - host = vmess.TransportConfig.Host - } - if host != "" { - uriConfig = append(uriConfig, fmt.Sprintf("obfs-host=%s", host)) - } - return strings.Join(uriConfig, ",") + "\r\n" -} diff --git a/pkg/adapter/shadowrocket/build.go b/pkg/adapter/shadowrocket/build.go deleted file mode 100644 index 33143a7..0000000 --- a/pkg/adapter/shadowrocket/build.go +++ /dev/null @@ -1,48 +0,0 @@ -package shadowrocket - -import ( - "fmt" - "time" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/general" - - "encoding/base64" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" - "github.com/perfect-panel/ppanel-server/pkg/traffic" -) - -type UserInfo struct { - Upload int64 - Download int64 - TotalTraffic int64 - ExpiredDate time.Time -} - -func BuildShadowrocket(servers []proxy.Proxy, uuid string, userinfo UserInfo) []byte { - upload := traffic.AutoConvert(userinfo.Upload, false) - download := traffic.AutoConvert(userinfo.Download, false) - total := traffic.AutoConvert(userinfo.TotalTraffic, false) - expiredAt := userinfo.ExpiredDate.Format("2006-01-02 15:04:05") - uri := fmt.Sprintf("STATUS=🚀↑:%s,↓:%s,TOT:%s💡Expires:%s\r\n", upload, download, total, expiredAt) - for _, s := range servers { - switch s.Protocol { - case "vmess": - uri += buildVmess(s, uuid) - case "shadowsocks": - uri += general.ShadowsocksUri(s, uuid) + "\r\n" - case "trojan": - uri += general.TrojanUri(s, uuid) + "\r\n" - case "vless": - uri += general.VlessUri(s, uuid) + "\r\n" - case "hysteria2": - uri += general.Hysteria2Uri(s, uuid) + "\r\n" - case "tuic": - uri += general.TuicUri(s, uuid) + "\r\n" - default: - continue - } - } - - return []byte(base64.StdEncoding.EncodeToString([]byte(uri))) -} diff --git a/pkg/adapter/shadowrocket/shadowrocket_test.go b/pkg/adapter/shadowrocket/shadowrocket_test.go deleted file mode 100644 index 50b8932..0000000 --- a/pkg/adapter/shadowrocket/shadowrocket_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package shadowrocket - -import ( - "testing" - "time" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func createVMess() proxy.Proxy { - return proxy.Proxy{ - Name: "Vmess", - Server: "test.xxxx.com", - Port: 13002, - Protocol: "vmess", - Option: proxy.Vmess{ - Port: 13002, - Transport: "websocket", - TransportConfig: proxy.TransportConfig{ - Path: "/ws", - Host: "test.xx.com", - }, - Security: "none", - }, - } -} - -func createSS() proxy.Proxy { - return proxy.Proxy{ - Name: "Shadowsocks", - Server: "test.xxxx.com", - Port: 10301, - Protocol: "shadowsocks", - Option: proxy.Shadowsocks{ - Port: 10301, - Method: "aes-256-gcm", - ServerKey: "123456", - }, - } -} - -func createTrojan() proxy.Proxy { - - return proxy.Proxy{ - Name: "Trojan", - Server: "test.xxxx.com", - Port: 13002, - Protocol: "trojan", - Option: proxy.Trojan{ - Port: 13002, - Transport: "websocket", - TransportConfig: proxy.TransportConfig{ - Path: "/ws", - Host: "baidu.com", - }, - SecurityConfig: proxy.SecurityConfig{ - SNI: "baidu.com", - AllowInsecure: true, - }, - }, - } -} -func TestBuildShadowrocket(t *testing.T) { - s := []proxy.Proxy{ - createVMess(), - createSS(), - createTrojan(), - } - uri := BuildShadowrocket(s, "uuid", UserInfo{ - Upload: 1024, - Download: 1024, - TotalTraffic: 2048, - ExpiredDate: time.Now().AddDate(0, 0, 1), - }) - t.Log(string(uri)) -} diff --git a/pkg/adapter/shadowrocket/vmess.go b/pkg/adapter/shadowrocket/vmess.go deleted file mode 100644 index f309566..0000000 --- a/pkg/adapter/shadowrocket/vmess.go +++ /dev/null @@ -1,57 +0,0 @@ -package shadowrocket - -import ( - "fmt" - "strings" - - "encoding/base64" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func buildVmess(data proxy.Proxy, uuid string) string { - vmess := data.Option.(proxy.Vmess) - - userinfo := fmt.Sprintf("auto:%s@%s:%d", uuid, data.Server, data.Port) - // 准备 config,使用默认值 - config := map[string]interface{}{ - "tfo": 1, - "remark": data.Name, - "alterId": 0, - } - - // tls 配置 - if vmess.Security == "tls" { - config["tls"] = 1 - if vmess.SecurityConfig.AllowInsecure { - config["allowInsecure"] = 1 - } - if vmess.SecurityConfig.SNI != "" { - config["peer"] = vmess.SecurityConfig.SNI - } - } - - // transport 配置 - switch vmess.Transport { - case "websocket": - config["obfs"] = "websocket" - if vmess.TransportConfig.Path != "" { - config["path"] = vmess.TransportConfig.Path - } - if vmess.TransportConfig.Host != "" { - config["obfsParam"] = vmess.TransportConfig.Host - } - case "grpc": - config["obfs"] = "grpc" - if vmess.TransportConfig.ServiceName != "" { - config["path"] = vmess.TransportConfig.ServiceName - } - } - query := make([]string, 0) - for k, v := range config { - query = append(query, fmt.Sprintf("%s=%v", k, v)) - } - queryStr := strings.Join(query, "&") - uri := fmt.Sprintf("vmess://%s?%s\r\n", base64.StdEncoding.EncodeToString([]byte(userinfo)), queryStr) - return uri -} diff --git a/pkg/adapter/singbox/build.go b/pkg/adapter/singbox/build.go deleted file mode 100644 index 30fbd61..0000000 --- a/pkg/adapter/singbox/build.go +++ /dev/null @@ -1,201 +0,0 @@ -package singbox - -import ( - "encoding/json" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" - "github.com/perfect-panel/ppanel-server/pkg/logger" -) - -func BuildSingbox(adapter proxy.Adapter, uuid string) ([]byte, error) { - // build outbounds type is Proxy - var proxies []Proxy - // build outbound group - for _, group := range adapter.Group { - if group.Type == proxy.GroupTypeSelect { - selector := Proxy{ - Type: Selector, - Tag: group.Name, - SelectorOptions: &SelectorOutboundOptions{ - OutboundOptions: OutboundOptions{ - Tag: group.Name, - Type: Selector, - }, - Outbounds: group.Proxies, - Default: group.Proxies[0], - InterruptExistConnections: false, - }, - } - proxies = append(proxies, selector) - } else if group.Type == proxy.GroupTypeURLTest { - selector := Proxy{ - Type: URLTest, - Tag: group.Name, - URLTestOptions: &URLTestOutboundOptions{ - OutboundOptions: OutboundOptions{ - Tag: group.Name, - Type: URLTest, - }, - Outbounds: group.Proxies, - URL: group.URL, - }, - } - proxies = append(proxies, selector) - } else { - logger.Errorf("[sing-box] Unknown group type: %s, group name: %s", group.Type, group.Name) - } - } - - // build outbounds - for _, data := range adapter.Proxies { - p := buildProxy(data, uuid) - if p == nil { - continue - } - proxies = append(proxies, *p) - } - - // add direct outbound - direct := Proxy{ - Type: Direct, - Tag: "DIRECT", - } - // add block outbound - block := Proxy{ - Type: Block, - Tag: "block", - } - // add dns outbound - dns := Proxy{ - Type: DNS, - Tag: "dns-out", - } - proxies = append(proxies, direct, block, dns) - - var rawConfig map[string]any - if err := json.Unmarshal([]byte(DefaultTemplate), &rawConfig); err != nil { - return nil, err - } - - rawConfig["outbounds"] = proxies - route := RouteOptions{ - Final: "手动选择", - Rules: []Rule{ - { - Inbound: []string{ - "tun-in", - "mixed-in", - }, - Action: "sniff", - }, - { - Type: "logical", - Mode: "or", - Rules: []Rule{ - { - Port: []uint16{53}, - }, - { - Protocol: []string{"dns"}, - }, - }, - Action: "hijack-dns", - }, - { - RuleSet: []string{ - "geosite-category-ads-all", - }, - ClashMode: "rule", - Action: "reject", - }, - { - ClashMode: "direct", - Outbound: "DIRECT", - }, - { - ClashMode: "global", - Outbound: "手动选择", - }, - { - IPIsPrivate: true, - Outbound: "DIRECT", - }, - { - RuleSet: []string{ - "geosite-private", - }, - Outbound: "DIRECT", - }, - }, - RuleSet: []RuleSet{ - { - Tag: "geoip-cn", - Type: "remote", - Format: "binary", - URL: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geoip/cn.srs", - DownloadDetour: "DIRECT", - }, - { - Tag: "geosite-cn", - Type: "remote", - Format: "binary", - URL: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/cn.srs", - DownloadDetour: "DIRECT", - }, - { - Tag: "geosite-private", - Type: "remote", - Format: "binary", - URL: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/private.srs", - DownloadDetour: "DIRECT", - }, - { - Tag: "geosite-category-ads-all", - Type: "remote", - Format: "binary", - URL: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/category-ads-all.srs", - DownloadDetour: "DIRECT", - }, - { - Tag: "geosite-geolocation-!cn", - Type: "remote", - Format: "binary", - URL: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/geolocation-!cn.srs", - DownloadDetour: "DIRECT", - }, - }, - AutoDetectInterface: true, - } - route.Rules = append(route.Rules, adapterToSingboxRule(adapter.Rules)...) - rawConfig["route"] = route - return json.Marshal(rawConfig) -} - -func buildProxy(data proxy.Proxy, uuid string) *Proxy { - var p *Proxy - var err error - switch data.Protocol { - case VLESS: - p, err = ParseVless(data, uuid) - case Shadowsocks: - p, err = ParseShadowsocks(data, uuid) - case Trojan: - p, err = ParseTrojan(data, uuid) - case VMess: - p, err = ParseVMess(data, uuid) - - case Hysteria2: - p, err = ParseHysteria2(data, uuid) - - case TUIC: - p, err = ParseTUIC(data, uuid) - - default: - logger.Error("Unknown protocol", logger.Field("protocol", data.Protocol), logger.Field("server", data.Name)) - } - if err != nil { - logger.Error("ParseVless", logger.Field("error", err.Error()), logger.Field("server", data.Name), logger.Field("protocol", data.Protocol)) - return nil - } - return p -} diff --git a/pkg/adapter/singbox/default.go b/pkg/adapter/singbox/default.go deleted file mode 100644 index d73b236..0000000 --- a/pkg/adapter/singbox/default.go +++ /dev/null @@ -1,100 +0,0 @@ -package singbox - -const DefaultTemplate = ` -{ - "log": { - "level": "info", - "timestamp": true - }, - "experimental": { - "clash_api": { - "external_controller": "127.0.0.1:9090", - "external_ui": "ui", - "secret": "", - "external_ui_download_url": "https://mirror.ghproxy.com/https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip", - "external_ui_download_detour": "direct", - "default_mode": "rule" - }, - "cache_file": { - "enabled": true, - "store_fakeip": false - } - }, - "dns": { - "servers": [ - { - "tag": "dns_proxy", - "address": "tls://8.8.8.8", - "detour": "手动选择" - }, - { - "tag": "dns_direct", - "address": "https://223.5.5.5/dns-query", - "detour": "DIRECT" - } - ], - "rules": [ - { - "outbound": "any", - "server": "dns_direct", - "disable_cache": true - }, - { - "rule_set": "geosite-cn", - "server": "dns_direct" - }, - { - "clash_mode": "direct", - "server": "dns_direct" - }, - { - "clash_mode": "global", - "server": "dns_proxy" - }, - { - "rule_set": "geosite-geolocation-!cn", - "server": "dns_proxy" - } - ], - "final": "dns_direct", - "strategy": "ipv4_only" - }, - "route": { - "rules": [ - { - "action": "sniff" - }, - { - "protocol": "dns", - "action": "hijack-dns" - } - ] - }, - "inbounds": [ - { - "tag": "tun-in", - "type": "tun", - "address": [ - "172.18.0.1/30", - "fdfe:dcba:9876::1/126" - ], - "auto_route": true, - "strict_route": true, - "stack": "system", - "platform": { - "http_proxy": { - "enabled": true, - "server": "127.0.0.1", - "server_port": 7890 - } - } - }, - { - "tag": "mixed-in", - "type": "mixed", - "listen": "127.0.0.1", - "listen_port": 7890 - } - ] -} -` diff --git a/pkg/adapter/singbox/hysteria2.go b/pkg/adapter/singbox/hysteria2.go deleted file mode 100644 index e7b9f55..0000000 --- a/pkg/adapter/singbox/hysteria2.go +++ /dev/null @@ -1,76 +0,0 @@ -package singbox - -import ( - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -type Hysteria2Obfs struct { - Type string `json:"type,omitempty"` - Password string `json:"password,omitempty"` -} - -type Hysteria2OutboundOptions struct { - ServerOptions - ServerPorts []string `json:"server_ports,omitempty"` - HopInterval int `json:"hop_interval,omitempty"` - UpMbps int `json:"up_mbps,omitempty"` - DownMbps int `json:"down_mbps,omitempty"` - Obfs *Hysteria2Obfs `json:"obfs,omitempty"` - Password string `json:"password,omitempty"` - Network string `json:"network,omitempty"` - OutboundTLSOptionsContainer - Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"` - Transport *V2RayTransportOptions `json:"transport,omitempty"` -} - -func ParseHysteria2(data proxy.Proxy, password string) (*Proxy, error) { - hysteria2 := data.Option.(proxy.Hysteria2) - - p := &Proxy{ - Tag: data.Name, - Type: Hysteria2, - Hysteria2Options: &Hysteria2OutboundOptions{ - ServerOptions: ServerOptions{ - Tag: data.Name, - Type: Hysteria2, - Server: data.Server, - }, - Password: password, - }, - } - - var ports []string - - if hysteria2.HopPorts != "" { - ps := strings.Split(hysteria2.HopPorts, ",") - for _, port := range ps { - // 舍弃单个端口,只保留端口范围 - if len(strings.Split(port, "-")) > 1 { - tmp := strings.Split(port, "-") - ports = append(ports, strings.Join(tmp, ":")) - } - } - - } - if len(ports) > 0 { - p.Hysteria2Options.ServerPorts = ports - p.Hysteria2Options.HopInterval = hysteria2.HopInterval - } else { - p.Hysteria2Options.ServerPort = data.Port - } - - if hysteria2.ObfsPassword != "" { - p.Hysteria2Options.Obfs = &Hysteria2Obfs{ - Type: "salamander", - Password: hysteria2.ObfsPassword, - } - } - var tls *OutboundTLSOptions - if hysteria2.SecurityConfig.SNI != "" { - tls = NewOutboundTLSOptions("tls", hysteria2.SecurityConfig) - } - p.Hysteria2Options.TLS = tls - return p, nil -} diff --git a/pkg/adapter/singbox/multiplex.go b/pkg/adapter/singbox/multiplex.go deleted file mode 100644 index 7188f95..0000000 --- a/pkg/adapter/singbox/multiplex.go +++ /dev/null @@ -1,17 +0,0 @@ -package singbox - -type OutboundMultiplexOptions struct { - Enabled bool `json:"enabled,omitempty"` - Protocol string `json:"protocol,omitempty"` - MaxConnections int `json:"max_connections,omitempty"` - MinStreams int `json:"min_streams,omitempty"` - MaxStreams int `json:"max_streams,omitempty"` - Padding bool `json:"padding,omitempty"` - Brutal *BrutalOptions `json:"brutal,omitempty"` -} - -type BrutalOptions struct { - Enabled bool `json:"enabled,omitempty"` - UpMbps int `json:"up_mbps,omitempty"` - DownMbps int `json:"down_mbps,omitempty"` -} diff --git a/pkg/adapter/singbox/rule.go b/pkg/adapter/singbox/rule.go deleted file mode 100644 index af00e56..0000000 --- a/pkg/adapter/singbox/rule.go +++ /dev/null @@ -1,130 +0,0 @@ -package singbox - -import ( - "strconv" - - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/rules" -) - -type Rule struct { - Outbound string `json:"outbound,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - RuleSet []string `json:"rule_set,omitempty"` - Domain []string `json:"domain,omitempty"` - DomainSuffix []string `json:"domain_suffix,omitempty"` - DomainKeyword []string `json:"domain_keyword,omitempty"` - DomainRegex []string `json:"domain_regex,omitempty"` - GeoIP []string `json:"geoip,omitempty"` - IPCIDR []string `json:"ip_cidr,omitempty"` - IPIsPrivate bool `json:"ip_is_private,omitempty"` - SourceIPCIDR []string `json:"source_ip_cidr,omitempty"` - ProcessName []string `json:"process_name,omitempty"` - ProcessPath []string `json:"process_path,omitempty"` - SourcePort []uint16 `json:"source_port,omitempty"` - Protocol []string `json:"protocol,omitempty"` - Port []uint16 `json:"port,omitempty"` - Action string `json:"action,omitempty"` - Inbound []string `json:"inbound,omitempty"` - Rules []Rule `json:"rules,omitempty"` - Type string `json:"type,omitempty"` - Mode string `json:"mode,omitempty"` -} - -type RuleSet struct { - Tag string `json:"tag,omitempty"` - Type string `json:"type,omitempty"` - Format string `json:"format,omitempty"` - URL string `json:"url,omitempty"` - DownloadDetour string `json:"download_detour,omitempty"` -} - -func adapterToSingboxRule(texts []string) []Rule { - var rulesList []Rule - for _, rule := range texts { - r := rules.NewRule(rule, "") - if r == nil { - continue - } - rulesList = addRuleToItem(rulesList, r.Target, *r) - } - return rulesList -} - -func addRuleToItem(group []Rule, outbound string, rule rules.Rule) []Rule { - for i := range group { - if group[i].Outbound == outbound { - switch rules.ParseRuleType(rule.Type) { - case rules.Domain: - group[i].Domain = append(group[i].Domain, rule.Payload) - return group - case rules.DomainSuffix: - group[i].DomainSuffix = append(group[i].DomainSuffix, rule.Payload) - return group - case rules.DomainKeyword: - group[i].DomainKeyword = append(group[i].DomainKeyword, rule.Payload) - return group - case rules.IPCIDR: - group[i].IPCIDR = append(group[i].IPCIDR, rule.Payload) - return group - case rules.SrcIPCIDR: - group[i].SourceIPCIDR = append(group[i].SourceIPCIDR, rule.Payload) - return group - case rules.SrcPort: - port, err := strconv.ParseUint(rule.Payload, 10, 16) - if err != nil { - logger.Errorf("[adapterToSingboxRule] failed to parse port %s to uint16", rule.Payload) - return group - } - group[i].SourcePort = append(group[i].SourcePort, uint16(port)) - return group - case rules.GEOIP: - group[i].GeoIP = append(group[i].GeoIP, rule.Payload) - return group - case rules.Process: - group[i].ProcessName = append(group[i].ProcessName, rule.Payload) - return group - case rules.ProcessPath: - group[i].ProcessPath = append(group[i].ProcessPath, rule.Payload) - return group - default: - logger.Errorf("[adapterToSingboxRule] unknown rule type %s", rule.Type) - return group - } - } - } - newRule := Rule{ - Outbound: outbound, - } - - switch rules.ParseRuleType(rule.Type) { - case rules.Domain: - newRule.Domain = []string{rule.Payload} - case rules.DomainSuffix: - newRule.DomainSuffix = []string{rule.Payload} - case rules.DomainKeyword: - newRule.DomainKeyword = []string{rule.Payload} - case rules.IPCIDR: - newRule.IPCIDR = []string{rule.Payload} - case rules.SrcIPCIDR: - newRule.SourceIPCIDR = []string{rule.Payload} - case rules.SrcPort: - port, err := strconv.ParseUint(rule.Payload, 10, 16) - if err != nil { - logger.Errorf("[adapterToSingboxRule] failed to parse port %s to uint16", rule.Payload) - return group - } - newRule.SourcePort = []uint16{uint16(port)} - case rules.GEOIP: - newRule.GeoIP = []string{rule.Payload} - case rules.Process: - newRule.ProcessName = []string{rule.Payload} - case rules.ProcessPath: - newRule.ProcessPath = []string{rule.Payload} - default: - logger.Errorf("[adapterToSingboxRule] unknown rule type %s", rule.Type) - return group - } - group = append(group, newRule) - return group -} diff --git a/pkg/adapter/singbox/rule_test.go b/pkg/adapter/singbox/rule_test.go deleted file mode 100644 index 95f8ce3..0000000 --- a/pkg/adapter/singbox/rule_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package singbox - -import ( - "fmt" - "testing" -) - -func TestAdapterToSingboxRule(t *testing.T) { - rules := []string{ - "DOMAIN,example.com,DIRECT", - "DOMAIN-SUFFIX,google.com,智能线路", - } - result := adapterToSingboxRule(rules) - fmt.Printf("TestAdapterToSingboxRule: result: %+v\n", result) -} diff --git a/pkg/adapter/singbox/shadowsocks.go b/pkg/adapter/singbox/shadowsocks.go deleted file mode 100644 index e0d59b0..0000000 --- a/pkg/adapter/singbox/shadowsocks.go +++ /dev/null @@ -1,34 +0,0 @@ -package singbox - -import ( - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -type ShadowsocksOptions struct { - ServerOptions - Method string `json:"method,omitempty"` - Password string `json:"password,omitempty"` - Plugin string `json:"plugin,omitempty"` - PluginOptions string `json:"plugin_opts,omitempty"` - Network string `json:"network,omitempty"` -} - -func ParseShadowsocks(data proxy.Proxy, uuid string) (*Proxy, error) { - config := data.Option.(proxy.Shadowsocks) - p := &Proxy{ - Tag: data.Name, - Type: Shadowsocks, - ShadowsocksOptions: &ShadowsocksOptions{ - ServerOptions: ServerOptions{ - Tag: data.Name, - Type: Shadowsocks, - Server: data.Server, - ServerPort: data.Port, - }, - Method: config.Method, - Password: uuid, - Network: "tcp", - }, - } - return p, nil -} diff --git a/pkg/adapter/singbox/singbox.go b/pkg/adapter/singbox/singbox.go deleted file mode 100644 index 0cb40f5..0000000 --- a/pkg/adapter/singbox/singbox.go +++ /dev/null @@ -1,98 +0,0 @@ -package singbox - -import ( - "encoding/json" - "fmt" -) - -const ( - Trojan = "trojan" - VLESS = "vless" - VMess = "vmess" - TUIC = "tuic" - Hysteria2 = "hysteria2" - Shadowsocks = "shadowsocks" - Selector = "selector" - URLTest = "urltest" - Direct = "direct" - Block = "block" - DNS = "dns" -) - -type Proxy struct { - Tag string `json:"tag,omitempty"` - Type string `json:"type"` - ShadowsocksOptions *ShadowsocksOptions `json:"-"` - TUICOptions *TUICOutboundOptions `json:"-"` - TrojanOptions *TrojanOutboundOptions `json:"-"` - VLESSOptions *VLESSOutboundOptions `json:"-"` - VMessOptions *VMessOutboundOptions `json:"-"` - Hysteria2Options *Hysteria2OutboundOptions `json:"-"` - SelectorOptions *SelectorOutboundOptions `json:"-"` - URLTestOptions *URLTestOutboundOptions `json:"-"` -} - -type ServerOptions struct { - Tag string `json:"tag"` - Type string `json:"type"` - Server string `json:"server"` - ServerPort int `json:"server_port,omitempty"` -} -type OutboundOptions struct { - Tag string `json:"tag"` - Type string `json:"type"` -} -type SelectorOutboundOptions struct { - OutboundOptions - Outbounds []string `json:"outbounds"` - Default string `json:"default,omitempty"` - InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` -} - -type URLTestOutboundOptions struct { - OutboundOptions - Outbounds []string `json:"outbounds"` - URL string `json:"url,omitempty"` - Interval Duration `json:"interval,omitempty"` - Tolerance uint16 `json:"tolerance,omitempty"` - IdleTimeout Duration `json:"idle_timeout,omitempty"` - InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` -} - -type RouteOptions struct { - Rules []Rule `json:"rules,omitempty"` - Final string `json:"final,omitempty"` - RuleSet []RuleSet `json:"rule_set,omitempty"` - AutoDetectInterface bool `json:"auto_detect_interface,omitempty"` -} - -func (p Proxy) MarshalJSON() ([]byte, error) { - type Alias Proxy - aux := struct { - Alias - }{ - Alias: (Alias)(p), - } - switch p.Type { - case Shadowsocks: - return json.Marshal(p.ShadowsocksOptions) - case TUIC: - return json.Marshal(p.TUICOptions) - case Trojan: - return json.Marshal(p.TrojanOptions) - case VLESS: - return json.Marshal(p.VLESSOptions) - case VMess: - return json.Marshal(p.VMessOptions) - case Hysteria2: - return json.Marshal(p.Hysteria2Options) - case Selector: - return json.Marshal(p.SelectorOptions) - case URLTest: - return json.Marshal(p.URLTestOptions) - case Direct, Block, DNS: - return json.Marshal(aux.Alias) - default: - return nil, fmt.Errorf("[sing-box] MarshalJSON unknown type: %s", p.Type) - } -} diff --git a/pkg/adapter/singbox/singbox_test.go b/pkg/adapter/singbox/singbox_test.go deleted file mode 100644 index 0d9c335..0000000 --- a/pkg/adapter/singbox/singbox_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package singbox - -import ( - "testing" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" - - "github.com/stretchr/testify/assert" -) - -func createSS() proxy.Proxy { - c := proxy.Shadowsocks{ - Method: "aes-256-gcm", - Port: 10301, - ServerKey: "", - } - return proxy.Proxy{ - Name: "Shadowsocks", - Server: "127.0.0.1", - Port: 10301, - Protocol: "shadowsocks", - Option: c, - } -} - -func createVLESS() proxy.Proxy { - c := proxy.Vless{ - Port: 10301, - Flow: "xtls-rprx-direct", - Transport: "websocket", - TransportConfig: proxy.TransportConfig{ - Path: "/ws", - Host: "baidu.com", - }, - Security: "tls", - SecurityConfig: proxy.SecurityConfig{ - SNI: "baidu.com", - Fingerprint: "chrome", - AllowInsecure: true, - }, - } - s := proxy.Proxy{ - Name: "VLESS", - Server: "test.xxx.com", - Port: 10301, - Protocol: "vless", - Option: c, - } - return s -} - -func TestSingboxShadowsocks(t *testing.T) { - s := createSS() - p, err := ParseShadowsocks(s, "uuid") - if err != nil { - t.Fatal(err) - } - data, err := p.MarshalJSON() - if err != nil { - t.Fatal(err) - } - assert.NotEqual(t, 0, len(data)) - - // Output: - // proxy: proxy: {"tag":"Shadowsocks","type":"shadowsocks","server":"127.0.0.1","server_port":10301,"method":"aes-256-gcm","password":"uuid","network":"tcp"} - -} - -func TestSingboxVless(t *testing.T) { - s := createVLESS() - p, err := ParseVless(s, "uuid") - if err != nil { - t.Fatal(err) - } - data, err := p.MarshalJSON() - if err != nil { - t.Fatal(err) - } - assert.NotEqual(t, 0, len(data)) -} diff --git a/pkg/adapter/singbox/tls.go b/pkg/adapter/singbox/tls.go deleted file mode 100644 index fa7c1f9..0000000 --- a/pkg/adapter/singbox/tls.go +++ /dev/null @@ -1,87 +0,0 @@ -package singbox - -import ( - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -type OutboundTLSOptions struct { - Enabled bool `json:"enabled,omitempty"` - DisableSNI bool `json:"disable_sni,omitempty"` - ServerName string `json:"server_name,omitempty"` - Insecure bool `json:"insecure,omitempty"` - ALPN Listable[string] `json:"alpn,omitempty"` - MinVersion string `json:"min_version,omitempty"` - MaxVersion string `json:"max_version,omitempty"` - CipherSuites Listable[string] `json:"cipher_suites,omitempty"` - Certificate Listable[string] `json:"certificate,omitempty"` - CertificatePath string `json:"certificate_path,omitempty"` - ECH *OutboundECHOptions `json:"ech,omitempty"` - UTLS *OutboundUTLSOptions `json:"utls,omitempty"` - Reality *OutboundRealityOptions `json:"reality,omitempty"` -} - -func NewOutboundTLSOptions(security string, cfg proxy.SecurityConfig) *OutboundTLSOptions { - var tls = &OutboundTLSOptions{} - switch security { - case "none": - return nil - case "tls": - tls.Enabled = true - if cfg.SNI != "" { - tls.ServerName = cfg.SNI - } else { - tls.DisableSNI = true - } - tls.Insecure = cfg.AllowInsecure - if cfg.Fingerprint != "" { - tls.UTLS = &OutboundUTLSOptions{ - Enabled: true, - Fingerprint: cfg.Fingerprint, - } - } - case "reality": - tls.Enabled = true - if cfg.SNI != "" { - tls.ServerName = cfg.SNI - } else { - tls.DisableSNI = true - } - tls.Insecure = cfg.AllowInsecure - if cfg.Fingerprint != "" { - tls.UTLS = &OutboundUTLSOptions{ - Enabled: true, - Fingerprint: cfg.Fingerprint, - } - } - tls.Reality = &OutboundRealityOptions{ - Enabled: true, - PublicKey: cfg.RealityPublicKey, - ShortID: cfg.RealityShortId, - } - } - return tls -} - -type OutboundECHOptions struct { - Enabled bool `json:"enabled,omitempty"` - PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` - DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` - Config Listable[string] `json:"config,omitempty"` - ConfigPath string `json:"config_path,omitempty"` -} - -type OutboundRealityOptions struct { - Enabled bool `json:"enabled,omitempty"` - PublicKey string `json:"public_key,omitempty"` - ShortID string `json:"short_id,omitempty"` -} - -type OutboundUTLSOptions struct { - Enabled bool `json:"enabled,omitempty"` - Fingerprint string `json:"fingerprint,omitempty"` -} -type Listable[T any] []T - -type OutboundTLSOptionsContainer struct { - TLS *OutboundTLSOptions `json:"tls,omitempty"` -} diff --git a/pkg/adapter/singbox/tool.go b/pkg/adapter/singbox/tool.go deleted file mode 100644 index 535dbe8..0000000 --- a/pkg/adapter/singbox/tool.go +++ /dev/null @@ -1,11 +0,0 @@ -package singbox - -import "encoding/json" - -func mergeOptions(target map[string]any, options any) error { - optionsJSON, err := json.Marshal(options) - if err != nil { - return err - } - return json.Unmarshal(optionsJSON, &target) -} diff --git a/pkg/adapter/singbox/trojan.go b/pkg/adapter/singbox/trojan.go deleted file mode 100644 index 9233ff1..0000000 --- a/pkg/adapter/singbox/trojan.go +++ /dev/null @@ -1,39 +0,0 @@ -package singbox - -import ( - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -type TrojanOutboundOptions struct { - ServerOptions - Password string `json:"password"` - Network string `json:"network,omitempty"` - OutboundTLSOptionsContainer - Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"` - Transport *V2RayTransportOptions `json:"transport,omitempty"` -} - -func ParseTrojan(data proxy.Proxy, uuid string) (*Proxy, error) { - trojan := data.Option.(proxy.Trojan) - p := &Proxy{ - Tag: data.Name, - Type: Trojan, - TrojanOptions: &TrojanOutboundOptions{ - ServerOptions: ServerOptions{ - Tag: data.Name, - Type: Trojan, - Server: data.Server, - ServerPort: data.Port, - }, - Password: uuid, - }, - } - // Transport options - transport := NewV2RayTransportOptions(trojan.Transport, trojan.TransportConfig) - - p.TrojanOptions.Transport = transport - // Security options - p.TrojanOptions.TLS = NewOutboundTLSOptions(trojan.Security, trojan.SecurityConfig) - return p, nil - -} diff --git a/pkg/adapter/singbox/tuic.go b/pkg/adapter/singbox/tuic.go deleted file mode 100644 index 9abdab2..0000000 --- a/pkg/adapter/singbox/tuic.go +++ /dev/null @@ -1,40 +0,0 @@ -package singbox - -import ( - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -type TUICOutboundOptions struct { - ServerOptions - UUID string `json:"uuid,omitempty"` - Password string `json:"password,omitempty"` - CongestionControl string `json:"congestion_control,omitempty"` - UDPRelayMode string `json:"udp_relay_mode,omitempty"` - UDPOverStream bool `json:"udp_over_stream,omitempty"` - ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` - Heartbeat string `json:"heartbeat,omitempty"` - Network string `json:"network,omitempty"` - OutboundTLSOptionsContainer -} - -func ParseTUIC(data proxy.Proxy, uuid string) (*Proxy, error) { - tuic := data.Option.(proxy.Tuic) - p := &Proxy{ - Tag: data.Name, - Type: TUIC, - TUICOptions: &TUICOutboundOptions{ - ServerOptions: ServerOptions{ - Tag: data.Name, - Type: TUIC, - Server: data.Server, - ServerPort: data.Port, - }, - UUID: uuid, - Password: uuid, - CongestionControl: "bbr", - }, - } - // Security options - p.TUICOptions.TLS = NewOutboundTLSOptions("tls", tuic.SecurityConfig) - return p, nil -} diff --git a/pkg/adapter/singbox/v2rayTransport.go b/pkg/adapter/singbox/v2rayTransport.go deleted file mode 100644 index 9ba3f10..0000000 --- a/pkg/adapter/singbox/v2rayTransport.go +++ /dev/null @@ -1,114 +0,0 @@ -package singbox - -import ( - "encoding/json" - "time" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -type V2RayTransportOptions struct { - Type string `json:"type"` - HTTPOptions V2RayHTTPOptions `json:"-"` - WebsocketOptions V2RayWebsocketOptions `json:"-"` - QUICOptions V2RayQUICOptions `json:"-"` - GRPCOptions V2RayGRPCOptions `json:"-"` - HTTPUpgradeOptions V2RayHTTPUpgradeOptions `json:"-"` -} - -func (v V2RayTransportOptions) MarshalJSON() ([]byte, error) { - var v2rayTransportOptions any - data := map[string]any{ - "type": v.Type, - } - switch v.Type { - case "http": - v2rayTransportOptions = v.HTTPOptions - case "ws": - v2rayTransportOptions = v.WebsocketOptions - case "quic": - v2rayTransportOptions = v.QUICOptions - case "grpc": - v2rayTransportOptions = v.GRPCOptions - case "httpupgrade": - v2rayTransportOptions = v.HTTPUpgradeOptions - } - if err := mergeOptions(data, v2rayTransportOptions); err != nil { - return nil, err - } - return json.Marshal(data) -} - -func NewV2RayTransportOptions(network string, transport proxy.TransportConfig) *V2RayTransportOptions { - var t *V2RayTransportOptions = nil - switch network { - case "websocket": - t = &V2RayTransportOptions{ - Type: "ws", - WebsocketOptions: V2RayWebsocketOptions{ - Path: transport.Path, - Headers: map[string]Listable[string]{ - "Host": []string{transport.Host}, - }, - MaxEarlyData: 2048, - EarlyDataHeaderName: "Sec-WebSocket-Protocol", - }, - } - case "httpupgrade": - t = &V2RayTransportOptions{ - Type: "httpupgrade", - HTTPOptions: V2RayHTTPOptions{ - Path: transport.Path, - Host: []string{transport.Host}, - Headers: map[string]Listable[string]{ - "Host": []string{transport.Host}, - }, - }, - } - - case "grpc": - t = &V2RayTransportOptions{ - Type: "grpc", - GRPCOptions: V2RayGRPCOptions{ - ServiceName: transport.ServiceName, - }, - } - } - return t -} - -type V2RayHTTPOptions struct { - Host Listable[string] `json:"host,omitempty"` - Path string `json:"path,omitempty"` - Method string `json:"method,omitempty"` - Headers HTTPHeader `json:"headers,omitempty"` - IdleTimeout Duration `json:"idle_timeout,omitempty"` - PingTimeout Duration `json:"ping_timeout,omitempty"` -} - -type V2RayWebsocketOptions struct { - Path string `json:"path,omitempty"` - Headers HTTPHeader `json:"headers,omitempty"` - MaxEarlyData uint32 `json:"max_early_data,omitempty"` - EarlyDataHeaderName string `json:"early_data_header_name,omitempty"` -} - -type V2RayQUICOptions struct{} - -type V2RayGRPCOptions struct { - ServiceName string `json:"service_name,omitempty"` - IdleTimeout string `json:"idle_timeout,omitempty"` - PingTimeout string `json:"ping_timeout,omitempty"` - PermitWithoutStream bool `json:"permit_without_stream,omitempty"` - ForceLite bool `json:"-"` // for test -} - -type V2RayHTTPUpgradeOptions struct { - Host string `json:"host,omitempty"` - Path string `json:"path,omitempty"` - Headers HTTPHeader `json:"headers,omitempty"` -} - -type HTTPHeader map[string]Listable[string] - -type Duration time.Duration diff --git a/pkg/adapter/singbox/vless.go b/pkg/adapter/singbox/vless.go deleted file mode 100644 index e1038ed..0000000 --- a/pkg/adapter/singbox/vless.go +++ /dev/null @@ -1,44 +0,0 @@ -package singbox - -import ( - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -type VLESSOutboundOptions struct { - ServerOptions - OutboundTLSOptionsContainer - UUID string `json:"uuid"` - Flow string `json:"flow,omitempty"` - Network string `json:"network,omitempty"` - Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"` - Transport *V2RayTransportOptions `json:"transport,omitempty"` - PacketEncoding *string `json:"packet_encoding,omitempty"` -} - -func ParseVless(data proxy.Proxy, uuid string) (*Proxy, error) { - vless := data.Option.(proxy.Vless) - packetEncoding := "xudp" - p := &Proxy{ - Tag: data.Name, - Type: VLESS, - VLESSOptions: &VLESSOutboundOptions{ - ServerOptions: ServerOptions{ - Tag: data.Name, - Type: VLESS, - Server: data.Server, - ServerPort: data.Port, - }, - UUID: uuid, - Flow: vless.Flow, - PacketEncoding: &packetEncoding, - }, - } - // Transport options - transport := NewV2RayTransportOptions(vless.Transport, vless.TransportConfig) - p.VLESSOptions.Transport = transport - - // Security options - p.VLESSOptions.TLS = NewOutboundTLSOptions(vless.Security, vless.SecurityConfig) - - return p, nil -} diff --git a/pkg/adapter/singbox/vmess.go b/pkg/adapter/singbox/vmess.go deleted file mode 100644 index daf062f..0000000 --- a/pkg/adapter/singbox/vmess.go +++ /dev/null @@ -1,43 +0,0 @@ -package singbox - -import ( - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -type VMessOutboundOptions struct { - ServerOptions - UUID string `json:"uuid"` - Security string `json:"security"` - AlterId int `json:"alter_id,omitempty"` - GlobalPadding bool `json:"global_padding,omitempty"` - AuthenticatedLength bool `json:"authenticated_length,omitempty"` - Network string `json:"network,omitempty"` - PacketEncoding string `json:"packet_encoding,omitempty"` - Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"` - Transport *V2RayTransportOptions `json:"transport,omitempty"` - OutboundTLSOptionsContainer -} - -func ParseVMess(data proxy.Proxy, uuid string) (*Proxy, error) { - vmess := data.Option.(proxy.Vmess) - p := &Proxy{ - Type: VMess, - VMessOptions: &VMessOutboundOptions{ - ServerOptions: ServerOptions{ - Tag: data.Name, - Type: VMess, - Server: data.Server, - ServerPort: data.Port, - }, - UUID: uuid, - Security: "auto", - AlterId: 0, - }, - } - // Transport options - p.VMessOptions.Transport = NewV2RayTransportOptions(vmess.Transport, vmess.TransportConfig) - // Security options - p.VMessOptions.TLS = NewOutboundTLSOptions(vmess.Security, vmess.SecurityConfig) - - return p, nil -} diff --git a/pkg/adapter/surfboard/build.go b/pkg/adapter/surfboard/build.go deleted file mode 100644 index 879a602..0000000 --- a/pkg/adapter/surfboard/build.go +++ /dev/null @@ -1,111 +0,0 @@ -package surfboard - -import ( - "bytes" - "embed" - "fmt" - "net/url" - "strings" - "text/template" - "time" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" - - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/traffic" -) - -//go:embed *.tpl -var configFiles embed.FS -var shadowsocksSupportMethod = []string{"aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305"} - -func BuildSurfboard(servers proxy.Adapter, siteName string, user UserInfo) []byte { - var proxies, proxyGroup string - for _, node := range servers.Proxies { - if uri := buildProxy(node, user.UUID); uri != "" { - proxies += uri - } - } - - for _, group := range servers.Group { - if group.Type == proxy.GroupTypeSelect { - proxyGroup += fmt.Sprintf("%s = select, %s", group.Name, strings.Join(group.Proxies, ", ")) + "\r\n" - } else if group.Type == proxy.GroupTypeURLTest { - proxyGroup += fmt.Sprintf("%s = url-test, %s, url=%s, interval=%d", group.Name, strings.Join(group.Proxies, ", "), group.URL, group.Interval) + "\r\n" - } else if group.Type == proxy.GroupTypeFallback { - proxyGroup += fmt.Sprintf("%s = fallback, %s, url=%s, interval=%d", group.Name, strings.Join(group.Proxies, ", "), group.URL, group.Interval) + "\r\n" - } else { - logger.Errorf("[BuildSurfboard] unknown group type: %s", group.Type) - } - } - - var rules string - for _, rule := range servers.Rules { - if rule == "" { - continue - } - rules += rule + "\r\n" - } - - //final rule - rules += "# 最终规则" + "\r\n" + "FINAL, 手动选择" - - file, err := configFiles.ReadFile("default.tpl") - if err != nil { - logger.Errorf("read default surfboard config error: %v", err.Error()) - return nil - } - // replace template - tpl, err := template.New("default").Parse(string(file)) - if err != nil { - logger.Errorf("read default surfboard config error: %v", err.Error()) - return nil - } - var buf bytes.Buffer - - var expiredAt string - if user.ExpiredDate.Before(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)) { - expiredAt = "长期有效" - } else { - expiredAt = user.ExpiredDate.Format("2006-01-02 15:04:05") - } - // convert traffic - upload := traffic.AutoConvert(user.Upload, false) - download := traffic.AutoConvert(user.Download, false) - total := traffic.AutoConvert(user.TotalTraffic, false) - unusedTraffic := traffic.AutoConvert(user.TotalTraffic-user.Upload-user.Download, false) - // query Host - urlParse, err := url.Parse(user.SubscribeURL) - if err != nil { - return nil - } - if err := tpl.Execute(&buf, map[string]interface{}{ - "Proxies": proxies, - "ProxyGroup": proxyGroup, - "SubscribeURL": user.SubscribeURL, - "SubscribeInfo": fmt.Sprintf("title=%s订阅信息, content=上传流量:%s\\n下载流量:%s\\n剩余流量: %s\\n套餐流量:%s\\n到期时间:%s", siteName, upload, download, unusedTraffic, total, expiredAt), - "SubscribeDomain": urlParse.Host, - "Rules": rules, - }); err != nil { - logger.Errorf("build surfboard config error: %v", err.Error()) - return nil - } - return buf.Bytes() -} - -func buildProxy(data proxy.Proxy, uuid string) string { - var p string - switch data.Protocol { - case "vmess": - p = buildVMess(data, uuid) - case "shadowsocks": - if !tool.Contains(shadowsocksSupportMethod, data.Option.(proxy.Shadowsocks).Method) { - return "" - } - p = buildShadowsocks(data, uuid) - case "trojan": - p = buildTrojan(data, uuid) - } - return p -} diff --git a/pkg/adapter/surfboard/build_test.go b/pkg/adapter/surfboard/build_test.go deleted file mode 100644 index f3773b1..0000000 --- a/pkg/adapter/surfboard/build_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package surfboard - -import ( - "testing" - "time" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" - - "github.com/perfect-panel/ppanel-server/pkg/uuidx" -) - -func TestBuildSurfboard(t *testing.T) { - siteName := "test" - user := UserInfo{ - UUID: uuidx.NewUUID().String(), - Upload: 0, - Download: 0, - TotalTraffic: 0, - ExpiredDate: time.Now().AddDate(0, 1, 1), - SubscribeURL: "https://test.com", - } - conf := BuildSurfboard(proxy.Adapter{}, siteName, user) - t.Log(string(conf)) -} diff --git a/pkg/adapter/surfboard/default.tpl b/pkg/adapter/surfboard/default.tpl deleted file mode 100644 index e30aac0..0000000 --- a/pkg/adapter/surfboard/default.tpl +++ /dev/null @@ -1,29 +0,0 @@ -#!MANAGED-CONFIG {{ .SubscribeURL }} interval=43200 strict=true - -[General] -loglevel = notify -ipv6 = false -skip-proxy = localhost, *.local, injections.adguard.org, local.adguard.org, 0.0.0.0/8, 10.0.0.0/8, 17.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 192.168.0.0/16, 192.88.99.0/24, 198.18.0.0/15, 198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/4, 240.0.0.0/4, 255.255.255.255/32 -tls-provider = default -show-error-page-for-reject = true -dns-server = 223.6.6.6, 119.29.29.29, 119.28.28.28 -test-timeout = 5 -internet-test-url = http://bing.com -proxy-test-url = http://bing.com - -[Panel] -SubscribeInfo = {{ .SubscribeInfo }}, style=info - -# Surfboard 配置文档:https://manual.getsurfboard.com/ - -[Proxy] -# 代理列表 -{{ .Proxies }} - -[Proxy Group] -# 代理组列表 -{{ .ProxyGroup }} - -[Rule] -# 规则列表 -{{ .Rules }} diff --git a/pkg/adapter/surfboard/model.go b/pkg/adapter/surfboard/model.go deleted file mode 100644 index 29d53ba..0000000 --- a/pkg/adapter/surfboard/model.go +++ /dev/null @@ -1,12 +0,0 @@ -package surfboard - -import "time" - -type UserInfo struct { - UUID string - Upload int64 - Download int64 - TotalTraffic int64 - ExpiredDate time.Time - SubscribeURL string -} diff --git a/pkg/adapter/surfboard/shadowsocks.go b/pkg/adapter/surfboard/shadowsocks.go deleted file mode 100644 index a8d03b4..0000000 --- a/pkg/adapter/surfboard/shadowsocks.go +++ /dev/null @@ -1,24 +0,0 @@ -package surfboard - -import ( - "fmt" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func buildShadowsocks(data proxy.Proxy, uuid string) string { - ss, ok := data.Option.(proxy.Shadowsocks) - if !ok { - return "" - } - addr := fmt.Sprintf("%s=ss, %s, %d", data.Name, data.Server, data.Port) - config := []string{ - addr, - fmt.Sprintf("encrypt-method=%s", ss.Method), - fmt.Sprintf("password=%s", uuid), - "tfo=true", - "udp-relay=true", - } - return strings.Join(config, ",") + "\r\n" -} diff --git a/pkg/adapter/surfboard/shadowsocks_test.go b/pkg/adapter/surfboard/shadowsocks_test.go deleted file mode 100644 index a07e2b6..0000000 --- a/pkg/adapter/surfboard/shadowsocks_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package surfboard - -import ( - "testing" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func createSS() proxy.Proxy { - return proxy.Proxy{ - Name: "Shadowsocks", - Server: "test.xxxx.com", - Port: 10301, - Protocol: "shadowsocks", - Option: proxy.Shadowsocks{ - Port: 10301, - Method: "aes-256-gcm", - ServerKey: "123456", - }, - } -} - -func TestShadowsocks(t *testing.T) { - node := createSS() - uuid := "123456" - shadowsocks := buildShadowsocks(node, uuid) - t.Log(shadowsocks) -} diff --git a/pkg/adapter/surfboard/trojan.go b/pkg/adapter/surfboard/trojan.go deleted file mode 100644 index 86884b3..0000000 --- a/pkg/adapter/surfboard/trojan.go +++ /dev/null @@ -1,41 +0,0 @@ -package surfboard - -import ( - "strconv" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func buildTrojan(data proxy.Proxy, uuid string) string { - // $config = [ - // "{$server['name']}=trojan", - // "{$server['host']}", - // "{$server['port']}", - // "password={$password}", - // $protocol_settings['server_name'] ? "sni={$protocol_settings['server_name']}" : "", - // 'tfo=true', - // 'udp-relay=true' - //]; - trojan, ok := data.Option.(proxy.Trojan) - if !ok { - return "" - } - config := []string{ - data.Name + "=trojan", - data.Server, - strconv.Itoa(data.Port), - "password=" + uuid, - "tfo=true", - "udp-relay=true", - } - if trojan.SecurityConfig.SNI != "" { - config = append(config, "sni="+trojan.SecurityConfig.SNI) - } - if trojan.SecurityConfig.AllowInsecure { - config = append(config, "skip-cert-verify=true") - } else { - config = append(config, "skip-cert-verify=false") - } - return strings.Join(config, ",") + "\r\n" -} diff --git a/pkg/adapter/surfboard/trojan_test.go b/pkg/adapter/surfboard/trojan_test.go deleted file mode 100644 index b1938d9..0000000 --- a/pkg/adapter/surfboard/trojan_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package surfboard - -import ( - "testing" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func createTrojan() proxy.Proxy { - - return proxy.Proxy{ - Name: "Trojan", - Server: "test.xxxx.com", - Port: 13002, - Protocol: "trojan", - Option: proxy.Trojan{ - Port: 13002, - Transport: "websocket", - TransportConfig: proxy.TransportConfig{ - Path: "/ws", - Host: "baidu.com", - }, - SecurityConfig: proxy.SecurityConfig{ - SNI: "baidu.com", - AllowInsecure: true, - }, - }, - } -} - -func TestTrojan(t *testing.T) { - node := createTrojan() - uuid := "123456" - trojan := buildTrojan(node, uuid) - t.Log(trojan) -} diff --git a/pkg/adapter/surfboard/vmess.go b/pkg/adapter/surfboard/vmess.go deleted file mode 100644 index 0d8f57a..0000000 --- a/pkg/adapter/surfboard/vmess.go +++ /dev/null @@ -1,45 +0,0 @@ -package surfboard - -import ( - "fmt" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func buildVMess(data proxy.Proxy, uuid string) string { - vmess, ok := data.Option.(proxy.Vmess) - if !ok { - return "" - } - addr := fmt.Sprintf("%s=vmess, %s, %d", data.Name, data.Server, data.Port) - uriConfig := []string{ - addr, - fmt.Sprintf("username=%s", uuid), - "vmess-aead=true", - "tfo=true", - "udp-relay=true", - } - if vmess.Security == "tls" { - uriConfig = append(uriConfig, "tls=true") - if vmess.SecurityConfig.AllowInsecure { - uriConfig = append(uriConfig, "skip-cert-verify=true") - } else { - uriConfig = append(uriConfig, "skip-cert-verify=false") - } - if vmess.SecurityConfig.SNI != "" { - uriConfig = append(uriConfig, fmt.Sprintf("sni=%s", vmess.SecurityConfig.SNI)) - } - } - if vmess.Transport == "websocket" { - uriConfig = append(uriConfig, "ws=true") - if vmess.TransportConfig.Path != "" { - uriConfig = append(uriConfig, fmt.Sprintf("ws-path=%s", vmess.TransportConfig.Path)) - } - if vmess.TransportConfig.Host != "" { - uriConfig = append(uriConfig, fmt.Sprintf("ws-headers=Host:%s", vmess.TransportConfig.Host)) - } - } - - return strings.Join(uriConfig, ",") + "\r\n" -} diff --git a/pkg/adapter/surfboard/vmess_test.go b/pkg/adapter/surfboard/vmess_test.go deleted file mode 100644 index 4ccb912..0000000 --- a/pkg/adapter/surfboard/vmess_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package surfboard - -import ( - "testing" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func createVMess() proxy.Proxy { - - return proxy.Proxy{ - Name: "Vmess", - Server: "test.xxxx.com", - Port: 13002, - Protocol: "vmess", - Option: proxy.Vmess{ - Port: 13002, - Transport: "websocket", - TransportConfig: proxy.TransportConfig{ - Path: "/ws", - Host: "test.xx.com", - }, - Security: "none", - }, - } -} - -func TestVMess(t *testing.T) { - node := createVMess() - uuid := "123456" - p := buildVMess(node, uuid) - t.Log(p) -} diff --git a/pkg/adapter/surge/default.tpl b/pkg/adapter/surge/default.tpl deleted file mode 100644 index 7375b37..0000000 --- a/pkg/adapter/surge/default.tpl +++ /dev/null @@ -1,61 +0,0 @@ -#!MANAGED-CONFIG {{ .SubscribeURL }} interval=43200 strict=true -# Surge 的规则配置手册: https://manual.nssurge.com/ - -[General] -loglevel = notify -# 从 Surge iOS 4 / Surge Mac 3.3.0 起,工具开始支持 DoH -doh-server = https://doh.pub/dns-query -# https://dns.alidns.com/dns-query, https://13800000000.rubyfish.cn/, https://dns.google/dns-query -dns-server = 223.5.5.5, 114.114.114.114 -tun-excluded-routes = 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 192.168.0.0/16, 192.88.99.0/24, 198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/4, 255.255.255.255/32 -skip-proxy = localhost, *.local, injections.adguard.org, local.adguard.org, captive.apple.com, guzzoni.apple.com, 0.0.0.0/8, 10.0.0.0/8, 17.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 192.168.0.0/16, 192.88.99.0/24, 198.18.0.0/15, 198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/4, 240.0.0.0/4, 255.255.255.255/32 - -wifi-assist = true -allow-wifi-access = true -wifi-access-http-port = 6152 -wifi-access-socks5-port = 6153 -http-listen = 0.0.0.0:6152 -socks5-listen = 0.0.0.0:6153 - -external-controller-access = surgepasswd@0.0.0.0:6170 -replica = false - -tls-provider = openssl -network-framework = false -exclude-simple-hostnames = true -ipv6 = true - -test-timeout = 4 -proxy-test-url = http://www.gstatic.com/generate_204 -geoip-maxmind-url = https://unpkg.zhimg.com/rulestatic@1.0.1/Country.mmdb - -[Replica] -hide-apple-request = true -hide-crashlytics-request = true -use-keyword-filter = false -hide-udp = false - -[Panel] -SubscribeInfo = {{ .SubscribeInfo }}, style=info - -# ----------------------------- -# Surge 的几种策略配置规范,请参考 https://manual.nssurge.com/policy/proxy.html -# 不同的代理策略有*很多*可选参数,请参考上方连接的 Parameters 一段,根据需求自行添加参数。 -# -# Surge 现已支持 UDP 转发功能,请参考: https://trello.com/c/ugOMxD3u/53-udp-%E8%BD%AC%E5%8F%91 -# Surge 现已支持 TCP-Fast-Open 技术,请参考: https://trello.com/c/ij65BU6Q/48-tcp-fast-open-troubleshooting-guide -# Surge 现已支持 ss-libev 的全部加密方式和混淆,请参考: https://trello.com/c/BTr0vG1O/47-ss-libev-%E7%9A%84%E6%94%AF%E6%8C%81%E6%83%85%E5%86%B5 -# ----------------------------- - -[Proxy] -{{ .Proxies }} - -[Proxy Group] -# 代理组列表 -{{ .ProxyGroup }} - -[Rule] -{{ .Rules }} - -[URL Rewrite] -^https?://(www.)?(g|google).cn https://www.google.com 302 \ No newline at end of file diff --git a/pkg/adapter/surge/hysteria2.go b/pkg/adapter/surge/hysteria2.go deleted file mode 100644 index d2205e0..0000000 --- a/pkg/adapter/surge/hysteria2.go +++ /dev/null @@ -1,43 +0,0 @@ -package surge - -import ( - "fmt" - "strconv" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func buildHysteria2(data proxy.Proxy, uuid string) string { - hysteria2, ok := data.Option.(proxy.Hysteria2) - if !ok { - return "" - } - - var port int - if hysteria2.HopPorts != "" { - ports := strings.Split(hysteria2.HopPorts, ",") - p := ports[0] - if len(strings.Split(p, "-")) > 1 { - p = strings.Split(p, "-")[0] - } - port, _ = strconv.Atoi(p) - } else { - port = data.Port - } - - config := []string{ - fmt.Sprintf("%s=hysteria2,%s,%d", data.Name, data.Server, port), - "password=" + uuid, - "udp-relay=true", - } - if hysteria2.SecurityConfig.SNI != "" { - config = append(config, "sni="+hysteria2.SecurityConfig.SNI) - } - if hysteria2.SecurityConfig.AllowInsecure { - config = append(config, "skip-cert-verify=true") - } else { - config = append(config, "skip-cert-verify=false") - } - return strings.Join(config, ",") + "\r\n" -} diff --git a/pkg/adapter/surge/hysteria2_test.go b/pkg/adapter/surge/hysteria2_test.go deleted file mode 100644 index 73d4306..0000000 --- a/pkg/adapter/surge/hysteria2_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package surge - -import ( - "testing" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func TestBuildHysteria2(t *testing.T) { - tests := []struct { - name string - data proxy.Proxy - uuid string - expected string - }{ - { - name: "Valid Hysteria2 with HopPorts", - data: proxy.Proxy{ - Name: "test", - Server: "server.com", - Port: 443, - Option: proxy.Hysteria2{ - HopPorts: "1000-2000", - SecurityConfig: proxy.SecurityConfig{ - SNI: "example.com", - AllowInsecure: true, - }, - }, - }, - uuid: "test-uuid", - expected: "test=hysteria2,server.com,1000,password=test-uuid,udp-relay=true,sni=example.com,skip-cert-verify=true\r\n", - }, - { - name: "Valid Hysteria2 without HopPorts", - data: proxy.Proxy{ - Name: "test", - Server: "server.com", - Port: 443, - Option: proxy.Hysteria2{ - SecurityConfig: proxy.SecurityConfig{ - SNI: "example.com", - AllowInsecure: false, - }, - }, - }, - uuid: "test-uuid", - expected: "test=hysteria2,server.com,443,password=test-uuid,udp-relay=true,sni=example.com,skip-cert-verify=false\r\n", - }, - { - name: "Invalid Hysteria2 Option", - data: proxy.Proxy{ - Name: "test", - Server: "server.com", - Port: 443, - Option: nil, - }, - uuid: "test-uuid", - expected: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := buildHysteria2(tt.data, tt.uuid) - if result != tt.expected { - t.Errorf("expected %s, got %s", tt.expected, result) - } - }) - } -} diff --git a/pkg/adapter/surge/shadowsocks.go b/pkg/adapter/surge/shadowsocks.go deleted file mode 100644 index 25eef9c..0000000 --- a/pkg/adapter/surge/shadowsocks.go +++ /dev/null @@ -1,24 +0,0 @@ -package surge - -import ( - "fmt" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func buildShadowsocks(data proxy.Proxy, uuid string) string { - ss, ok := data.Option.(proxy.Shadowsocks) - if !ok { - return "" - } - addr := fmt.Sprintf("%s=ss, %s, %d", data.Name, data.Server, data.Port) - config := []string{ - addr, - fmt.Sprintf("encrypt-method=%s", ss.Method), - fmt.Sprintf("password=%s", uuid), - "tfo=true", - "udp-relay=true", - } - return strings.Join(config, ",") + "\r\n" -} diff --git a/pkg/adapter/surge/surge.go b/pkg/adapter/surge/surge.go deleted file mode 100644 index c6565a5..0000000 --- a/pkg/adapter/surge/surge.go +++ /dev/null @@ -1,117 +0,0 @@ -package surge - -import ( - "bytes" - "embed" - "fmt" - "net/url" - "strings" - "text/template" - "time" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/traffic" -) - -//go:embed *.tpl -var configFiles embed.FS - -type UserInfo struct { - UUID string - Upload int64 - Download int64 - TotalTraffic int64 - ExpiredDate time.Time - SubscribeURL string -} - -type Surge struct { - Adapter proxy.Adapter - UUID string - User UserInfo -} - -func NewSurge(adapter proxy.Adapter) *Surge { - return &Surge{ - Adapter: adapter, - } -} - -func (m *Surge) Build(uuid, siteName string, user UserInfo) []byte { - var proxies, proxyGroup, rules string - - for _, p := range m.Adapter.Proxies { - switch p.Protocol { - case "shadowsocks": - proxies += buildShadowsocks(p, uuid) - case "trojan": - proxies += buildTrojan(p, uuid) - case "hysteria2": - proxies += buildHysteria2(p, uuid) - case "vmess": - proxies += buildVMess(p, uuid) - } - } - for _, group := range m.Adapter.Group { - if group.Type == proxy.GroupTypeSelect { - proxyGroup += fmt.Sprintf("%s = select, %s", group.Name, strings.Join(group.Proxies, ", ")) + "\r\n" - } else if group.Type == proxy.GroupTypeURLTest { - proxyGroup += fmt.Sprintf("%s = url-test, %s, url=%s, interval=%d", group.Name, strings.Join(group.Proxies, ", "), group.URL, group.Interval) + "\r\n" - } else if group.Type == proxy.GroupTypeFallback { - proxyGroup += fmt.Sprintf("%s = fallback, %s, url=%s, interval=%d", group.Name, strings.Join(group.Proxies, ", "), group.URL, group.Interval) + "\r\n" - } else { - logger.Errorf("[BuildSurfboard] unknown group type: %s", group.Type) - } - } - for _, rule := range m.Adapter.Rules { - if rule == "" { - continue - } - rules += rule + "\r\n" - } - //final rule - rules += "# 最终规则" + "\r\n" + "FINAL,手动选择,dns-failed" - - file, err := configFiles.ReadFile("default.tpl") - if err != nil { - logger.Errorf("read default surfboard config error: %v", err.Error()) - return nil - } - // replace template - tpl, err := template.New("default").Parse(string(file)) - if err != nil { - logger.Errorf("read default surfboard config error: %v", err.Error()) - return nil - } - var buf bytes.Buffer - - var expiredAt string - if user.ExpiredDate.Before(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)) { - expiredAt = "长期有效" - } else { - expiredAt = user.ExpiredDate.Format("2006-01-02 15:04:05") - } - // convert traffic - upload := traffic.AutoConvert(user.Upload, false) - download := traffic.AutoConvert(user.Download, false) - total := traffic.AutoConvert(user.TotalTraffic, false) - unusedTraffic := traffic.AutoConvert(user.TotalTraffic-user.Upload-user.Download, false) - // query Host - urlParse, err := url.Parse(user.SubscribeURL) - if err != nil { - return nil - } - if err := tpl.Execute(&buf, map[string]interface{}{ - "Proxies": proxies, - "ProxyGroup": proxyGroup, - "SubscribeURL": user.SubscribeURL, - "SubscribeInfo": fmt.Sprintf("title=%s订阅信息, content=上传流量:%s\\n下载流量:%s\\n剩余流量: %s\\n套餐流量:%s\\n到期时间:%s", siteName, upload, download, unusedTraffic, total, expiredAt), - "SubscribeDomain": urlParse.Host, - "Rules": rules, - }); err != nil { - logger.Errorf("build Surge config error: %v", err.Error()) - return nil - } - return buf.Bytes() -} diff --git a/pkg/adapter/surge/surge_test.go b/pkg/adapter/surge/surge_test.go deleted file mode 100644 index efec1d5..0000000 --- a/pkg/adapter/surge/surge_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package surge - -import ( - "strings" - "testing" - "time" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func TestSurgeBuild(t *testing.T) { - adapter := proxy.Adapter{ - Proxies: []proxy.Proxy{ - { - Name: "test-shadowsocks", - Protocol: "shadowsocks", - Server: "1.2.3.4", - Port: 8388, - Option: proxy.Shadowsocks{ - Method: "aes-256-gcm", - }, - }, - { - Name: "test-trojan", - Protocol: "trojan", - Server: "5.6.7.8", - Port: 443, - Option: proxy.Trojan{ - SecurityConfig: proxy.SecurityConfig{ - SNI: "example.com", - AllowInsecure: true, - }, - }, - }, - { - Name: "test-hysteria", - Protocol: "hysteria2", - Server: "1.1.1.1", - Port: 443, - Option: proxy.Hysteria2{ - HopPorts: "8080-8090", - HopInterval: 320, - SecurityConfig: proxy.SecurityConfig{ - SNI: "example.com", - AllowInsecure: true, - }, - }, - }, - }, - Group: []proxy.Group{ - { - Name: "test-group", - Type: proxy.GroupTypeSelect, - Proxies: []string{"test-shadowsocks", "test-trojan", "test-hysteria"}, - }, - { - Name: "手动选择", - Type: proxy.GroupTypeSelect, - Proxies: []string{"test-shadowsocks", "test-trojan", "test-hysteria"}, - }, - }, - Rules: []string{ - "DOMAIN-SUFFIX,example.com,DIRECT", - }, - } - - user := UserInfo{ - UUID: "test-uuid", - Upload: 1024, - Download: 2048, - TotalTraffic: 4096, - ExpiredDate: time.Now().Add(24 * time.Hour), - SubscribeURL: "http://example.com/subscribe", - } - - surge := NewSurge(adapter) - config := surge.Build("test-uuid", "TestSite", user) - - if config == nil { - t.Fatal("Expected non-nil config") - } - - configStr := string(config) - t.Logf("configStr: %v", configStr) - if !strings.Contains(configStr, "test-shadowsocks=ss") { - t.Errorf("Expected config to contain test-shadowsocks proxy") - } - if !strings.Contains(configStr, "test-trojan=trojan") { - t.Errorf("Expected config to contain test-trojan proxy") - } - if !strings.Contains(configStr, "test-group = select") { - t.Errorf("Expected config to contain test-group proxy group") - } - if !strings.Contains(configStr, "DOMAIN-SUFFIX,example.com,DIRECT") { - t.Errorf("Expected config to contain rule for example.com") - } -} diff --git a/pkg/adapter/surge/trojan.go b/pkg/adapter/surge/trojan.go deleted file mode 100644 index 4d755c7..0000000 --- a/pkg/adapter/surge/trojan.go +++ /dev/null @@ -1,32 +0,0 @@ -package surge - -import ( - "strconv" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func buildTrojan(data proxy.Proxy, uuid string) string { - trojan, ok := data.Option.(proxy.Trojan) - if !ok { - return "" - } - config := []string{ - data.Name + "=trojan", - data.Server, - strconv.Itoa(data.Port), - "password=" + uuid, - "tfo=true", - "udp-relay=true", - } - if trojan.SecurityConfig.SNI != "" { - config = append(config, "sni="+trojan.SecurityConfig.SNI) - } - if trojan.SecurityConfig.AllowInsecure { - config = append(config, "skip-cert-verify=true") - } else { - config = append(config, "skip-cert-verify=false") - } - return strings.Join(config, ",") + "\r\n" -} diff --git a/pkg/adapter/surge/vmess.go b/pkg/adapter/surge/vmess.go deleted file mode 100644 index 0b2c95d..0000000 --- a/pkg/adapter/surge/vmess.go +++ /dev/null @@ -1,44 +0,0 @@ -package surge - -import ( - "fmt" - "strings" - - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" -) - -func buildVMess(data proxy.Proxy, uuid string) string { - vmess, ok := data.Option.(proxy.Vmess) - if !ok { - return "" - } - addr := fmt.Sprintf("%s=vmess, %s, %d", data.Name, data.Server, data.Port) - uriConfig := []string{ - addr, - fmt.Sprintf("username=%s", uuid), - "vmess-aead=true", - "tfo=true", - "udp-relay=true", - } - if vmess.Security == "tls" { - uriConfig = append(uriConfig, "tls=true") - if vmess.SecurityConfig.AllowInsecure { - uriConfig = append(uriConfig, "skip-cert-verify=true") - } else { - uriConfig = append(uriConfig, "skip-cert-verify=false") - } - if vmess.SecurityConfig.SNI != "" { - uriConfig = append(uriConfig, fmt.Sprintf("sni=%s", vmess.SecurityConfig.SNI)) - } - } - if vmess.Transport == "websocket" { - uriConfig = append(uriConfig, "ws=true") - if vmess.TransportConfig.Path != "" { - uriConfig = append(uriConfig, fmt.Sprintf("ws-path=%s", vmess.TransportConfig.Path)) - } - if vmess.TransportConfig.Host != "" { - uriConfig = append(uriConfig, fmt.Sprintf("ws-headers=Host:%s", vmess.TransportConfig.Host)) - } - } - return strings.Join(uriConfig, ",") + "\r\n" -} diff --git a/pkg/adapter/uilts.go b/pkg/adapter/uilts.go deleted file mode 100644 index 698c1ba..0000000 --- a/pkg/adapter/uilts.go +++ /dev/null @@ -1,197 +0,0 @@ -package adapter - -import ( - "encoding/json" - "strings" - - "github.com/perfect-panel/ppanel-server/internal/model/server" - "github.com/perfect-panel/ppanel-server/pkg/adapter/proxy" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/random" - "github.com/perfect-panel/ppanel-server/pkg/tool" -) - -func addNode(data *server.Server, host string, port int) *proxy.Proxy { - var option any - node := proxy.Proxy{ - Name: data.Name, - Server: host, - Port: port, - Country: data.Country, - Protocol: data.Protocol, - } - switch data.Protocol { - case "shadowsocks": - var ss proxy.Shadowsocks - if err := json.Unmarshal([]byte(data.Config), &ss); err != nil { - return nil - } - if port == 0 { - node.Port = ss.Port - } - option = ss - case "vless": - var vless proxy.Vless - if err := json.Unmarshal([]byte(data.Config), &vless); err != nil { - return nil - } - if port == 0 { - node.Port = vless.Port - } - option = vless - case "vmess": - var vmess proxy.Vmess - if err := json.Unmarshal([]byte(data.Config), &vmess); err != nil { - return nil - } - if port == 0 { - node.Port = vmess.Port - } - option = vmess - case "trojan": - var trojan proxy.Trojan - if err := json.Unmarshal([]byte(data.Config), &trojan); err != nil { - return nil - } - if port == 0 { - node.Port = trojan.Port - } - option = trojan - case "hysteria2": - var hysteria2 proxy.Hysteria2 - if err := json.Unmarshal([]byte(data.Config), &hysteria2); err != nil { - return nil - } - if port == 0 { - node.Port = hysteria2.Port - } - option = hysteria2 - case "tuic": - var tuic proxy.Tuic - if err := json.Unmarshal([]byte(data.Config), &tuic); err != nil { - return nil - } - if port == 0 { - node.Port = tuic.Port - } - option = tuic - default: - return nil - } - node.Option = option - return &node -} - -func addProxyToGroup(proxyName, groupName string, groups []proxy.Group) []proxy.Group { - for i, group := range groups { - if group.Name == groupName { - groups[i].Proxies = tool.RemoveDuplicateElements(append(group.Proxies, proxyName)...) - return groups - } - } - groups = append(groups, proxy.Group{ - Name: groupName, - Type: "select", - Proxies: []string{proxyName}, - }) - return groups -} - -func adapterRules(groups []*server.RuleGroup) (proxyGroup []proxy.Group, rules []string) { - for _, group := range groups { - proxyGroup = append(proxyGroup, proxy.Group{ - Name: group.Name, - Type: "select", - Proxies: RemoveEmptyString(strings.Split(group.Tags, ",")), - }) - rules = append(rules, strings.Split(group.Rules, "/n")...) - } - return -} - -func generateProxyGroup(servers []proxy.Proxy) (proxyGroup []proxy.Group, region []string) { - // 设置手动选择分组 - proxyGroup = append(proxyGroup, []proxy.Group{ - { - Name: "智能线路", - Type: "url-test", - Proxies: make([]string, 0), - URL: "https://www.gstatic.com/generate_204", - Interval: 300, - }, - { - Name: "手动选择", - Type: "select", - Proxies: []string{"智能线路"}, - }, - }...) - - for _, node := range servers { - if node.Country != "" { - proxyGroup = addProxyToGroup(node.Name, node.Country, proxyGroup) - region = append(region, node.Country) - proxyGroup = addProxyToGroup(node.Country, "智能线路", proxyGroup) - } - proxyGroup = addProxyToGroup(node.Name, "手动选择", proxyGroup) - } - proxyGroup = addProxyToGroup("DIRECT", "手动选择", proxyGroup) - return proxyGroup, tool.RemoveDuplicateElements(region...) -} - -func adapterProxies(servers []*server.Server) []proxy.Proxy { - var proxies []proxy.Proxy - for _, node := range servers { - switch node.RelayMode { - case server.RelayModeAll: - var relays []server.NodeRelay - if err := json.Unmarshal([]byte(node.RelayNode), &relays); err != nil { - logger.Errorw("Unmarshal RelayNode", logger.Field("error", err.Error()), logger.Field("node", node.Name), logger.Field("relayNode", node.RelayNode)) - continue - } - for _, relay := range relays { - n := addNode(node, relay.Host, relay.Port) - if n == nil { - continue - } - if relay.Prefix != "" { - n.Name = relay.Prefix + "-" + n.Name - } - proxies = append(proxies, *n) - } - case server.RelayModeRandom: - var relays []server.NodeRelay - if err := json.Unmarshal([]byte(node.RelayNode), &relays); err != nil { - logger.Errorw("Unmarshal RelayNode", logger.Field("error", err.Error()), logger.Field("node", node.Name), logger.Field("relayNode", node.RelayNode)) - continue - } - randNum := random.RandomInRange(0, len(relays)-1) - relay := relays[randNum] - n := addNode(node, relay.Host, relay.Port) - if n == nil { - continue - } - if relay.Prefix != "" { - n.Name = relay.Prefix + " - " + node.Name - } - proxies = append(proxies, *n) - default: - logger.Info("Not Relay Mode", logger.Field("node", node.Name), logger.Field("relayMode", node.RelayMode)) - n := addNode(node, node.ServerAddr, 0) - if n != nil { - proxies = append(proxies, *n) - } - } - } - return proxies -} - -// RemoveEmptyString 切片去除空值 -func RemoveEmptyString(arr []string) []string { - var result []string - for _, str := range arr { - if str != "" { - result = append(result, str) - } - } - return result -} diff --git a/pkg/cache/cacheopt.go b/pkg/cache/cacheopt.go new file mode 100644 index 0000000..836c12a --- /dev/null +++ b/pkg/cache/cacheopt.go @@ -0,0 +1,49 @@ +package cache + +import "time" + +const ( + defaultExpiry = time.Hour * 24 * 7 + defaultNotFoundExpiry = time.Minute +) + +type ( + // Options is used to store the cache options. + Options struct { + Expiry time.Duration + NotFoundExpiry time.Duration + } + + // Option defines the method to customize an Options. + Option func(o *Options) +) + +func newOptions(opts ...Option) Options { + var o Options + for _, opt := range opts { + opt(&o) + } + + if o.Expiry <= 0 { + o.Expiry = defaultExpiry + } + if o.NotFoundExpiry <= 0 { + o.NotFoundExpiry = defaultNotFoundExpiry + } + + return o +} + +// WithExpiry returns a func to customize an Options with given expiry. +func WithExpiry(expiry time.Duration) Option { + return func(o *Options) { + o.Expiry = expiry + } +} + +// WithNotFoundExpiry returns a func to customize an Options with given not found expiry. +func WithNotFoundExpiry(expiry time.Duration) Option { + return func(o *Options) { + o.NotFoundExpiry = expiry + } +} diff --git a/pkg/cache/cacheopt_test.go b/pkg/cache/cacheopt_test.go new file mode 100644 index 0000000..7b7d82c --- /dev/null +++ b/pkg/cache/cacheopt_test.go @@ -0,0 +1,28 @@ +package cache + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestCacheOptions(t *testing.T) { + t.Run("default options", func(t *testing.T) { + o := newOptions() + assert.Equal(t, defaultExpiry, o.Expiry) + assert.Equal(t, defaultNotFoundExpiry, o.NotFoundExpiry) + }) + + t.Run("with expiry", func(t *testing.T) { + o := newOptions(WithExpiry(time.Second)) + assert.Equal(t, time.Second, o.Expiry) + assert.Equal(t, defaultNotFoundExpiry, o.NotFoundExpiry) + }) + + t.Run("with not found expiry", func(t *testing.T) { + o := newOptions(WithNotFoundExpiry(time.Second)) + assert.Equal(t, defaultExpiry, o.Expiry) + assert.Equal(t, time.Second, o.NotFoundExpiry) + }) +} diff --git a/pkg/cache/gorm.go b/pkg/cache/gorm.go index 23552de..0fd2805 100644 --- a/pkg/cache/gorm.go +++ b/pkg/cache/gorm.go @@ -5,12 +5,16 @@ import ( "database/sql" "encoding/json" "errors" + "time" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) -var ErrNotFound = redis.Nil +var ( + // ErrNotFound is the error when cache not found. + ErrNotFound = redis.Nil +) type ( // ExecCtxFn defines the sql exec method. @@ -23,16 +27,21 @@ type ( QueryCtxFn func(conn *gorm.DB, v interface{}) error CachedConn struct { - db *gorm.DB - cache *redis.Client + db *gorm.DB + cache *redis.Client + expiry time.Duration + notFoundExpiry time.Duration } ) // NewConn returns a CachedConn with a redis cluster cache. -func NewConn(db *gorm.DB, c *redis.Client) CachedConn { +func NewConn(db *gorm.DB, c *redis.Client, opts ...Option) CachedConn { + o := newOptions(opts...) return CachedConn{ - db: db, - cache: c, + db: db, + cache: c, + expiry: o.Expiry, + notFoundExpiry: o.NotFoundExpiry, } } @@ -65,7 +74,7 @@ func (cc CachedConn) SetCache(key string, v interface{}) error { return err } // set redis key - return cc.cache.Set(context.Background(), key, val, 0).Err() + return cc.cache.Set(context.Background(), key, val, cc.expiry).Err() } // ExecCtx runs given exec on given keys, and returns execution result. diff --git a/pkg/cache/gorm_test.go b/pkg/cache/gorm_test.go index 76bf62e..0078884 100644 --- a/pkg/cache/gorm_test.go +++ b/pkg/cache/gorm_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/perfect-panel/ppanel-server/pkg/orm" + "github.com/perfect-panel/server/pkg/orm" "github.com/redis/go-redis/v9" "gorm.io/gorm" "gorm.io/plugin/soft_delete" diff --git a/pkg/constant/context.go b/pkg/constant/context.go index 4023cd1..45c7f86 100644 --- a/pkg/constant/context.go +++ b/pkg/constant/context.go @@ -8,4 +8,5 @@ const ( CtxKeyRequestHost CtxKey = "requestHost" CtxKeyPlatform CtxKey = "platform" CtxKeyPayment CtxKey = "payment" + LoginType CtxKey = "loginType" ) diff --git a/pkg/constant/types.go b/pkg/constant/types.go index 20238a9..a2db39b 100644 --- a/pkg/constant/types.go +++ b/pkg/constant/types.go @@ -1,8 +1,6 @@ package constant -import ( - "encoding/json" -) +import "encoding/json" // Used for type cloning conversion const ( @@ -43,9 +41,20 @@ type TemporaryOrderInfo struct { Identifier string `json:"identifier"` AuthType string `json:"auth_type"` Password string `json:"password"` + InviteCode string `json:"invite_code,omitempty"` } -func (t TemporaryOrderInfo) Marshal() string { - value, _ := json.Marshal(t) - return string(value) +func (t *TemporaryOrderInfo) Unmarshal(data []byte) error { + type Alias TemporaryOrderInfo + aux := (*Alias)(t) + return json.Unmarshal(data, aux) +} + +func (t *TemporaryOrderInfo) Marshal() ([]byte, error) { + type Alias TemporaryOrderInfo + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(t), + }) } diff --git a/pkg/constant/version.go b/pkg/constant/version.go index a964009..5370107 100644 --- a/pkg/constant/version.go +++ b/pkg/constant/version.go @@ -1,4 +1,7 @@ package constant // Version PPanel version -const Version = "0.3.0(3002)" +var ( + Version = "unknown version" + BuildTime = "unknown time" +) diff --git a/pkg/countryCenter/county_center.go b/pkg/countryCenter/county_center.go deleted file mode 100644 index 549b762..0000000 --- a/pkg/countryCenter/county_center.go +++ /dev/null @@ -1,1175 +0,0 @@ -package countryCenter - -import ( - "fmt" - "strings" -) - -var countryCenter = map[string][2]float64{ - "Afghanistan": {33.93911, 67.709953}, - "Albania": {41.153332, 20.168331}, - "Algeria": {28.033886, 1.659626}, - "Andorra": {42.546245, 1.601554}, - "Angola": {-11.202692, 17.873887}, - "Antigua and Barbuda": {17.060816, -61.796428}, - "Argentina": {-38.416097, -63.616672}, - "Armenia": {40.069099, 45.038189}, - "Australia": {-25.274398, 133.775136}, - "Azerbaijan": {40.143105, 47.576927}, - "Bahamas": {25.03428, -77.39628}, - "Bahrain": {26.0667, 50.5577}, - "Bangladesh": {23.684994, 90.356331}, - "Barbados": {13.193887, -59.543198}, - "Belarus": {53.709807, 27.953389}, - "Belgium": {50.503887, 4.469936}, - "Belize": {17.189877, -88.49765}, - "Benin": {9.30769, 2.315834}, - "Bhutan": {27.514162, 90.433601}, - "Bolivia": {-16.290154, -63.588653}, - "Bosnia and Herzegovina": {43.915886, 17.679076}, - "Botswana": {-22.328474, 24.684866}, - "Brazil": {-14.235004, -51.92528}, - "Brunei": {4.535277, 114.727669}, - "Bulgaria": {42.733883, 25.48583}, - "Burkina Faso": {12.238333, -1.561593}, - "Burundi": {-3.373056, 29.918886}, - "Cabo Verde": {16.5388, -23.0418}, - "Cambodia": {12.565679, 104.990963}, - "Cameroon": {7.369722, 12.354722}, - "Canada": {56.130366, -106.346771}, - "Central African Republic": {6.611111, 20.939444}, - "Chad": {15.454166, 18.732207}, - "Chile": {-35.675147, -71.542969}, - "China": {35.86166, 104.195397}, - "Colombia": {4.570868, -74.297333}, - "Comoros": {-11.6455, 43.3333}, - "Congo": {-0.228021, 15.827659}, - "Costa Rica": {9.748917, -83.753428}, - "Croatia": {45.1, 15.2}, - "Cuba": {21.521757, -77.781167}, - "Cyprus": {35.126413, 33.429859}, - "Czechia": {49.817492, 15.472962}, - "Denmark": {56.26392, 9.501785}, - "Djibouti": {11.825138, 42.590275}, - "Dominica": {15.414999, -61.370976}, - "Dominican Republic": {18.735693, -70.162651}, - "Ecuador": {-1.831239, -78.183406}, - "Egypt": {26.820553, 30.802498}, - "El Salvador": {13.794185, -88.89653}, - "Equatorial Guinea": {1.650801, 10.267895}, - "Eritrea": {15.179384, 39.782334}, - "Estonia": {58.595272, 25.013607}, - "Eswatini": {-26.522503, 31.465866}, - "Ethiopia": {9.145, 40.489673}, - "Fiji": {-17.713371, 178.065032}, - "Finland": {61.92411, 25.748151}, - "France": {46.603354, 1.888334}, - "Gabon": {-0.803689, 11.609444}, - "Gambia": {13.443182, -15.310139}, - "Georgia": {32.165622, -82.900075}, - "Germany": {51.165691, 10.451526}, - "Ghana": {7.946527, -1.023194}, - "Greece": {39.074208, 21.824312}, - "Grenada": {12.262776, -61.604171}, - "Guatemala": {15.783471, -90.230759}, - "Guinea": {9.945587, -9.696645}, - "Guinea-Bissau": {11.803749, -15.180413}, - "Guyana": {4.860416, -58.93018}, - "Haiti": {18.971187, -72.285215}, - "Honduras": {15.199999, -86.241905}, - "Hungary": {47.162494, 19.503304}, - "Iceland": {64.963051, -19.020835}, - "India": {20.593684, 78.96288}, - "Indonesia": {-0.789275, 113.921327}, - "Iran": {32.427908, 53.688046}, - "Iraq": {33.223191, 43.679291}, - "Ireland": {53.41291, -8.24389}, - "Israel": {31.046051, 34.851612}, - "Italy": {41.87194, 12.56738}, - "Jamaica": {18.109581, -77.297508}, - "Japan": {36.204824, 138.252924}, - "Jordan": {30.585164, 36.238414}, - "Kazakhstan": {48.019573, 66.923684}, - "Kenya": {-1.292066, 36.821946}, - "Kiribati": {-3.370417, -168.734039}, - "Kuwait": {29.31166, 47.481766}, - "Kyrgyzstan": {41.20438, 74.766098}, - "Laos": {19.85627, 102.495496}, - "Latvia": {56.879635, 24.603189}, - "Lebanon": {33.854721, 35.862285}, - "Lesotho": {-29.609988, 28.233608}, - "Liberia": {6.428055, -9.429499}, - "Libya": {26.3351, 17.228331}, - "Liechtenstein": {47.166, 9.555373}, - "Lithuania": {55.169438, 23.881275}, - "Luxembourg": {49.815273, 6.129583}, - "Madagascar": {-18.766947, 46.869107}, - "Malawi": {-13.254308, 34.301525}, - "Malaysia": {4.210484, 101.975766}, - "Maldives": {3.202778, 73.22068}, - "Mali": {17.570692, -3.996166}, - "Malta": {35.937496, 14.375416}, - "Marshall Islands": {7.131474, 171.184478}, - "Mauritania": {21.00789, -10.940835}, - "Mauritius": {-20.348404, 57.552152}, - "Mexico": {23.634501, -102.552784}, - "Micronesia": {7.425554, 150.550812}, - "Moldova": {47.411631, 28.369885}, - "Monaco": {43.738417, 7.424616}, - "Mongolia": {46.862496, 103.846656}, - "Montenegro": {42.708678, 19.37439}, - "Morocco": {31.791702, -7.09262}, - "Mozambique": {-18.665695, 35.529562}, - "Myanmar": {21.916221, 95.955974}, - "Namibia": {-22.95764, 18.49041}, - "Nauru": {-0.522778, 166.931503}, - "Nepal": {28.394857, 84.124008}, - "Netherlands": {52.132633, 5.291266}, - "New Zealand": {-40.900557, 174.885971}, - "Nicaragua": {12.865416, -85.207229}, - "Niger": {17.607789, 8.081666}, - "Nigeria": {9.081999, 8.675277}, - "North Korea": {40.339852, 127.510093}, - "North Macedonia": {41.608635, 21.745275}, - "Norway": {60.472024, 8.468946}, - "Oman": {21.512583, 55.923255}, - "Pakistan": {30.375321, 69.345116}, - "Palau": {7.51498, 134.58252}, - "Palestine": {31.952162, 35.233154}, - "Panama": {8.537981, -80.782127}, - "Papua New Guinea": {-6.314993, 143.95555}, - "Paraguay": {-23.442503, -58.443832}, - "Peru": {-9.189967, -75.015152}, - "Philippines": {12.879721, 121.774017}, - "Poland": {51.919438, 19.145136}, - "Portugal": {39.399872, -8.224454}, - "Qatar": {25.354826, 51.183884}, - "Romania": {45.943161, 24.96676}, - "Russia": {61.52401, 105.318756}, - "Rwanda": {-1.940278, 29.873888}, - "Saint Kitts and Nevis": {17.357822, -62.782998}, - "Saint Lucia": {13.909444, -60.978893}, - "Saint Vincent and the Grenadines": {12.984305, -61.287228}, - "Samoa": {-13.759029, -172.104629}, - "San Marino": {43.94236, 12.457777}, - "Sao Tome and Principe": {0.18636, 6.613081}, - "Saudi Arabia": {23.885942, 45.079162}, - "Senegal": {14.497401, -14.452362}, - "Serbia": {44.016521, 21.005859}, - "Seychelles": {-4.679574, 55.491977}, - "Sierra Leone": {8.460555, -11.779889}, - "Singapore": {1.352083, 103.819836}, - "Slovakia": {48.669026, 19.699024}, - "Slovenia": {46.151241, 14.995463}, - "Solomon Islands": {-9.64571, 160.156194}, - "Somalia": {5.152149, 46.199616}, - "South Africa": {-30.559482, 22.937506}, - "South Korea": {35.907757, 127.766922}, - "South Sudan": {6.8769919, 31.3069788}, - "Spain": {40.463667, -3.74922}, - "Sri Lanka": {7.873054, 80.771797}, - "Sudan": {12.862807, 30.217636}, - "Suriname": {3.919305, -56.027783}, - "Sweden": {60.128161, 18.643501}, - "Switzerland": {46.818188, 8.227512}, - "Syria": {34.802075, 38.996815}, - "Tajikistan": {38.861034, 71.276093}, - "Tanzania": {-6.369028, 34.888822}, - "Thailand": {15.870032, 100.992541}, - "Timor-Leste": {-8.874217, 125.727539}, - "Togo": {8.619543, 0.824782}, - "Tonga": {-21.178986, -175.198242}, - "Trinidad and Tobago": {10.691803, -61.222503}, - "Tunisia": {33.886917, 9.537499}, - "Turkey": {38.963745, 35.243322}, - "Turkmenistan": {38.969719, 59.556278}, - "Tuvalu": {-7.109535, 177.64933}, - "Uganda": {1.373333, 32.290275}, - "Ukraine": {48.379433, 31.16558}, - "United Arab Emirates": {23.424076, 53.847818}, - "United Kingdom": {55.378051, -3.435973}, - "United States": {37.09024, -95.712891}, - "Uruguay": {-32.522779, -55.765835}, - "Uzbekistan": {41.377491, 64.585262}, - "Vanuatu": {-15.376706, 166.959158}, - "Vatican City": {41.902916, 12.453389}, - "Venezuela": {6.42375, -66.58973}, - "Vietnam": {14.058324, 108.277199}, - "Yemen": {15.552727, 48.516388}, - "Zambia": {-13.133897, 27.849332}, - "Zimbabwe": {-19.015438, 29.154857}, -} - -// 国家简称到全称的映射表 -var countryAbbr = map[string]string{ - // ISO 3166-1 alpha-2 codes - "AD": "Andorra", - "AE": "United Arab Emirates", - "AF": "Afghanistan", - "AG": "Antigua and Barbuda", - "AI": "Anguilla", - "AL": "Albania", - "AM": "Armenia", - "AO": "Angola", - "AQ": "Antarctica", - "AR": "Argentina", - "AS": "American Samoa", - "AT": "Austria", - "AU": "Australia", - "AW": "Aruba", - "AX": "Aland Islands", - "AZ": "Azerbaijan", - "BA": "Bosnia and Herzegovina", - "BB": "Barbados", - "BD": "Bangladesh", - "BE": "Belgium", - "BF": "Burkina Faso", - "BG": "Bulgaria", - "BH": "Bahrain", - "BI": "Burundi", - "BJ": "Benin", - "BL": "Saint Barthelemy", - "BM": "Bermuda", - "BN": "Brunei", - "BO": "Bolivia", - "BQ": "Bonaire", - "BR": "Brazil", - "BS": "Bahamas", - "BT": "Bhutan", - "BV": "Bouvet Island", - "BW": "Botswana", - "BY": "Belarus", - "BZ": "Belize", - "CA": "Canada", - "CC": "Cocos Islands", - "CD": "Congo", - "CF": "Central African Republic", - "CG": "Congo", - "CH": "Switzerland", - "CI": "Cote D'Ivoire", - "CK": "Cook Islands", - "CL": "Chile", - "CM": "Cameroon", - "CN": "China", - "CO": "Colombia", - "CR": "Costa Rica", - "CU": "Cuba", - "CV": "Cabo Verde", - "CW": "Curacao", - "CX": "Christmas Island", - "CY": "Cyprus", - "CZ": "Czechia", - "DE": "Germany", - "DJ": "Djibouti", - "DK": "Denmark", - "DM": "Dominica", - "DO": "Dominican Republic", - "DZ": "Algeria", - "EC": "Ecuador", - "EE": "Estonia", - "EG": "Egypt", - "EH": "Western Sahara", - "ER": "Eritrea", - "ES": "Spain", - "ET": "Ethiopia", - "FI": "Finland", - "FJ": "Fiji", - "FK": "Falkland Islands", - "FM": "Micronesia", - "FO": "Faroe Islands", - "FR": "France", - "GA": "Gabon", - "GB": "United Kingdom", - "GD": "Grenada", - "GE": "Georgia", - "GF": "French Guiana", - "GG": "Guernsey", - "GH": "Ghana", - "GI": "Gibraltar", - "GL": "Greenland", - "GM": "Gambia", - "GN": "Guinea", - "GP": "Guadeloupe", - "GQ": "Equatorial Guinea", - "GR": "Greece", - "GS": "South Georgia", - "GT": "Guatemala", - "GU": "Guam", - "GW": "Guinea-Bissau", - "GY": "Guyana", - "HK": "Hong Kong", - "HM": "Heard Island", - "HN": "Honduras", - "HR": "Croatia", - "HT": "Haiti", - "HU": "Hungary", - "ID": "Indonesia", - "IE": "Ireland", - "IL": "Israel", - "IM": "Isle of Man", - "IN": "India", - "IO": "British Indian Ocean Territory", - "IQ": "Iraq", - "IR": "Iran", - "IS": "Iceland", - "IT": "Italy", - "JE": "Jersey", - "JM": "Jamaica", - "JO": "Jordan", - "JP": "Japan", - "KE": "Kenya", - "KG": "Kyrgyzstan", - "KH": "Cambodia", - "KI": "Kiribati", - "KM": "Comoros", - "KN": "Saint Kitts and Nevis", - "KP": "North Korea", - "KR": "South Korea", - "KW": "Kuwait", - "KY": "Cayman Islands", - "KZ": "Kazakhstan", - "LA": "Laos", - "LB": "Lebanon", - "LC": "Saint Lucia", - "LI": "Liechtenstein", - "LK": "Sri Lanka", - "LR": "Liberia", - "LS": "Lesotho", - "LT": "Lithuania", - "LU": "Luxembourg", - "LV": "Latvia", - "LY": "Libya", - "MA": "Morocco", - "MC": "Monaco", - "MD": "Moldova", - "ME": "Montenegro", - "MF": "Saint Martin", - "MG": "Madagascar", - "MH": "Marshall Islands", - "MK": "North Macedonia", - "ML": "Mali", - "MM": "Myanmar", - "MN": "Mongolia", - "MO": "Macao", - "MP": "Northern Mariana Islands", - "MQ": "Martinique", - "MR": "Mauritania", - "MS": "Montserrat", - "MT": "Malta", - "MU": "Mauritius", - "MV": "Maldives", - "MW": "Malawi", - "MX": "Mexico", - "MY": "Malaysia", - "MZ": "Mozambique", - "NA": "Namibia", - "NC": "New Caledonia", - "NE": "Niger", - "NF": "Norfolk Island", - "NG": "Nigeria", - "NI": "Nicaragua", - "NL": "Netherlands", - "NO": "Norway", - "NP": "Nepal", - "NR": "Nauru", - "NU": "Niue", - "NZ": "New Zealand", - "OM": "Oman", - "PA": "Panama", - "PE": "Peru", - "PF": "French Polynesia", - "PG": "Papua New Guinea", - "PH": "Philippines", - "PK": "Pakistan", - "PL": "Poland", - "PM": "Saint Pierre and Miquelon", - "PN": "Pitcairn", - "PR": "Puerto Rico", - "PS": "Palestine", - "PT": "Portugal", - "PW": "Palau", - "PY": "Paraguay", - "QA": "Qatar", - "RE": "Reunion", - "RO": "Romania", - "RS": "Serbia", - "RU": "Russia", - "RW": "Rwanda", - "SA": "Saudi Arabia", - "SB": "Solomon Islands", - "SC": "Seychelles", - "SD": "Sudan", - "SE": "Sweden", - "SG": "Singapore", - "SH": "Saint Helena", - "SI": "Slovenia", - "SJ": "Svalbard and Jan Mayen", - "SK": "Slovakia", - "SL": "Sierra Leone", - "SM": "San Marino", - "SN": "Senegal", - "SO": "Somalia", - "SR": "Suriname", - "SS": "South Sudan", - "ST": "Sao Tome and Principe", - "SV": "El Salvador", - "SX": "Sint Maarten", - "SY": "Syria", - "SZ": "Eswatini", - "TC": "Turks and Caicos Islands", - "TD": "Chad", - "TF": "French Southern Territories", - "TG": "Togo", - "TH": "Thailand", - "TJ": "Tajikistan", - "TK": "Tokelau", - "TL": "Timor-Leste", - "TM": "Turkmenistan", - "TN": "Tunisia", - "TO": "Tonga", - "TR": "Turkey", - "TT": "Trinidad and Tobago", - "TV": "Tuvalu", - "TW": "Taiwan", - "TZ": "Tanzania", - "UA": "Ukraine", - "UG": "Uganda", - "UM": "United States Minor Outlying Islands", - "US": "United States", - "UY": "Uruguay", - "UZ": "Uzbekistan", - "VA": "Vatican City", - "VC": "Saint Vincent and the Grenadines", - "VE": "Venezuela", - "VG": "British Virgin Islands", - "VI": "U.S. Virgin Islands", - "VN": "Vietnam", - "VU": "Vanuatu", - "WF": "Wallis and Futuna", - "WS": "Samoa", - "YE": "Yemen", - "YT": "Mayotte", - "ZA": "South Africa", - "ZM": "Zambia", - "ZW": "Zimbabwe", -} - -// 主要城市到国家的映射表 -var cityToCountry = map[string]string{ - // China - "Beijing": "China", "Shanghai": "China", "Guangzhou": "China", "Shenzhen": "China", "HK": "China", "Tsuen Wan": "China", - "Chengdu": "China", "Hangzhou": "China", "Wuhan": "China", "Xi'an": "China", - "Nanjing": "China", "Tianjin": "China", "Chongqing": "China", "Shenyang": "China", - "Dalian": "China", "Qingdao": "China", "Jinan": "China", "Harbin": "China", - "Changchun": "China", "Shijiazhuang": "China", "Taiyuan": "China", "Hohhot": "China", - "Yinchuan": "China", "Lanzhou": "China", "Xining": "China", "Urumqi": "China", - "Lhasa": "China", "Kunming": "China", "Guiyang": "China", "Nanning": "China", - "Haikou": "China", "Changsha": "China", "Zhengzhou": "China", "Nanchang": "China", - "Hefei": "China", "Fuzhou": "China", "Taipei": "China", "Hong Kong": "China", "Macao": "China", - - // United States - "New York": "United States", "Chicago": "United States", - "Houston": "United States", "Phoenix": "United States", "Philadelphia": "United States", - "San Antonio": "United States", "San Diego": "United States", "Dallas": "United States", - "San Jose": "United States", "Austin": "United States", "Jacksonville": "United States", - "Fort Worth": "United States", "Columbus": "United States", "Indianapolis": "United States", - "Charlotte": "United States", "San Francisco": "United States", "Seattle": "United States", - "Denver": "United States", "Washington": "United States", "Boston": "United States", - "El Paso": "United States", "Detroit": "United States", "Nashville": "United States", - "Portland Oregon": "United States", "Memphis": "United States", "Oklahoma City": "United States", - "Las Vegas": "United States", "Louisville": "United States", "Baltimore": "United States", - "Milwaukee": "United States", "Albuquerque": "United States", "Tucson": "United States", - "Fresno": "United States", "Sacramento": "United States", "Mesa": "United States", - "Kansas City": "United States", "Atlanta": "United States", "Long Beach": "United States", - "Colorado Springs": "United States", "Raleigh": "United States", "Miami": "United States", - "Virginia Beach": "United States", "Omaha": "United States", "Oakland": "United States", - "Minneapolis": "United States", "Tulsa": "United States", "Arlington": "United States", - - // Japan - "Tokyo": "Japan", "Osaka": "Japan", "Yokohama": "Japan", "Nagoya": "Japan", - "Sapporo": "Japan", "Fukuoka": "Japan", "Kobe": "Japan", "Kawasaki": "Japan", - "Kyoto": "Japan", "Saitama": "Japan", "Hiroshima": "Japan", "Sendai": "Japan", - "Kitakyushu": "Japan", "Chiba": "Japan", "Sakai": "Japan", "Niigata": "Japan", - "Hamamatsu": "Japan", "Okayama": "Japan", "Sagamihara": "Japan", "Kumamoto": "Japan", - - // United Kingdom - "London": "United Kingdom", "Birmingham": "United Kingdom", "Manchester": "United Kingdom", - "Glasgow": "United Kingdom", "Liverpool": "United Kingdom", "Edinburgh": "United Kingdom", - "Leeds": "United Kingdom", "Sheffield": "United Kingdom", "Bristol": "United Kingdom", - "Cardiff": "United Kingdom", "Belfast": "United Kingdom", "Leicester": "United Kingdom", - "Coventry": "United Kingdom", "Bradford": "United Kingdom", "Nottingham": "United Kingdom", - "Kingston upon Hull": "United Kingdom", "Newcastle upon Tyne": "United Kingdom", - "Stoke-on-Trent": "United Kingdom", "Southampton": "United Kingdom", "Derby": "United Kingdom", - - // Germany - "Berlin": "Germany", "Hamburg": "Germany", "Munich": "Germany", "Cologne": "Germany", - "Frankfurt": "Germany", "Stuttgart": "Germany", "Düsseldorf": "Germany", "Dortmund": "Germany", - "Essen": "Germany", "Leipzig": "Germany", "Bremen": "Germany", "Dresden": "Germany", - "Hanover": "Germany", "Nuremberg": "Germany", "Duisburg": "Germany", "Bochum": "Germany", - "Wuppertal": "Germany", "Bielefeld": "Germany", "Bonn": "Germany", "Münster": "Germany", - - // France - "Paris": "France", "Marseille": "France", "Lyon": "France", "Toulouse": "France", - "Nice": "France", "Nantes": "France", "Strasbourg": "France", "Montpellier": "France", - "Bordeaux": "France", "Lille": "France", "Rennes": "France", "Reims": "France", - "Le Havre": "France", "Saint-Étienne": "France", "Toulon": "France", "Grenoble": "France", - "Dijon": "France", "Angers": "France", "Nîmes": "France", "Villeurbanne": "France", - - // Italy - "Rome": "Italy", "Milan": "Italy", "Naples": "Italy", "Turin": "Italy", - "Palermo": "Italy", "Genoa": "Italy", "Bologna": "Italy", "Florence": "Italy", - "Bari": "Italy", "Catania": "Italy", "Venice": "Italy", "Verona": "Italy", - "Messina": "Italy", "Padua": "Italy", "Trieste": "Italy", "Taranto": "Italy", - - // Spain - "Madrid": "Spain", "Seville": "Spain", - "Zaragoza": "Spain", "Málaga": "Spain", "Murcia": "Spain", "Palma": "Spain", - "Las Palmas": "Spain", "Bilbao": "Spain", "Alicante": "Spain", - "Valladolid": "Spain", "Vigo": "Spain", "Gijón": "Spain", "Hospitalet": "Spain", - "A Coruña": "Spain", "Vitoria-Gasteiz": "Spain", "Granada": "Spain", "Elche": "Spain", - - // Canada - "Toronto": "Canada", "Montreal": "Canada", "Vancouver": "Canada", "Calgary": "Canada", - "Edmonton": "Canada", "Ottawa": "Canada", "Winnipeg": "Canada", "Quebec City": "Canada", - "Hamilton Canada": "Canada", "Kitchener": "Canada", "London Ontario": "Canada", "Victoria Canada": "Canada", - "Halifax": "Canada", "Oshawa": "Canada", "Windsor Canada": "Canada", "Saskatoon": "Canada", - "Regina": "Canada", "St. John's": "Canada", "Kelowna": "Canada", "Barrie": "Canada", - - // Australia - "Sydney": "Australia", "Melbourne": "Australia", "Brisbane": "Australia", "Perth": "Australia", - "Adelaide": "Australia", "Gold Coast": "Australia", "Newcastle Australia": "Australia", "Canberra": "Australia", - "Sunshine Coast": "Australia", "Wollongong": "Australia", "Hobart": "Australia", "Geelong": "Australia", - "Townsville": "Australia", "Cairns": "Australia", "Toowoomba": "Australia", "Darwin": "Australia", - - // India - "Mumbai": "India", "Delhi": "India", "Bangalore": "India", "Hyderabad": "India", - "Ahmedabad": "India", "Chennai": "India", "Kolkata": "India", "Surat": "India", - "Pune": "India", "Jaipur": "India", "Lucknow": "India", "Kanpur": "India", - "Nagpur": "India", "Indore": "India", "Thane": "India", "Bhopal": "India", - "Visakhapatnam": "India", "Pimpri": "India", "Patna": "India", "Vadodara": "India", - "Ludhiana": "India", "Agra": "India", "Nashik": "India", "Faridabad": "India", - "Meerut": "India", "Rajkot": "India", "Kalyan": "India", "Vasai": "India", - - // Brazil - "São Paulo": "Brazil", "Rio de Janeiro": "Brazil", "Brasília": "Brazil", "Salvador": "Brazil", - "Fortaleza": "Brazil", "Belo Horizonte": "Brazil", "Manaus": "Brazil", "Curitiba": "Brazil", - "Recife": "Brazil", "Porto Alegre": "Brazil", "Belém": "Brazil", "Goiânia": "Brazil", - "Guarulhos": "Brazil", "Campinas": "Brazil", "São Luís": "Brazil", "São Gonçalo": "Brazil", - - // Russia - "Moscow": "Russia", "Saint Petersburg": "Russia", "Novosibirsk": "Russia", "Yekaterinburg": "Russia", - "Nizhny Novgorod": "Russia", "Kazan": "Russia", "Chelyabinsk": "Russia", "Omsk": "Russia", - "Samara": "Russia", "Rostov-on-Don": "Russia", "Ufa": "Russia", "Krasnoyarsk": "Russia", - "Perm": "Russia", "Voronezh": "Russia", "Volgograd": "Russia", "Krasnodar": "Russia", - - // South Korea - "Seoul": "South Korea", "Busan": "South Korea", "Incheon": "South Korea", "Daegu": "South Korea", - "Daejeon": "South Korea", "Gwangju": "South Korea", "Suwon": "South Korea", "Ulsan": "South Korea", - "Changwon": "South Korea", "Goyang": "South Korea", "Yongin": "South Korea", "Bucheon": "South Korea", - - // Mexico - "Mexico City": "Mexico", "Guadalajara": "Mexico", "Monterrey": "Mexico", "Puebla": "Mexico", - "Tijuana": "Mexico", "León": "Mexico", "Juárez": "Mexico", "Torreón": "Mexico", - "Querétaro": "Mexico", "San Luis Potosí": "Mexico", "Mexicali": "Mexico", - - // Indonesia - "Jakarta": "Indonesia", "Surabaya": "Indonesia", "Bandung": "Indonesia", "Bekasi": "Indonesia", - "Medan": "Indonesia", "Tangerang": "Indonesia", "Depok": "Indonesia", "Semarang": "Indonesia", - "Palembang": "Indonesia", "Makassar": "Indonesia", "Batam": "Indonesia", "Bogor": "Indonesia", - - // Turkey - "Istanbul": "Turkey", "Ankara": "Turkey", "Izmir": "Turkey", "Bursa": "Turkey", - "Adana": "Turkey", "Gaziantep": "Turkey", "Konya": "Turkey", "Antalya": "Turkey", - "Kayseri": "Turkey", "Mersin": "Turkey", "Eskişehir": "Turkey", "Diyarbakır": "Turkey", - - // Netherlands - "Amsterdam": "Netherlands", "Rotterdam": "Netherlands", "The Hague": "Netherlands", "Utrecht": "Netherlands", - "Eindhoven": "Netherlands", "Tilburg": "Netherlands", "Groningen": "Netherlands", "Almere": "Netherlands", - "Breda": "Netherlands", "Nijmegen": "Netherlands", "Enschede": "Netherlands", "Haarlem": "Netherlands", - - // Saudi Arabia - "Riyadh": "Saudi Arabia", "Jeddah": "Saudi Arabia", "Mecca": "Saudi Arabia", "Medina": "Saudi Arabia", - "Dammam": "Saudi Arabia", "Khobar": "Saudi Arabia", "Tabuk": "Saudi Arabia", "Buraidah": "Saudi Arabia", - "Khamis Mushait": "Saudi Arabia", "Hafar Al-Batin": "Saudi Arabia", "Jubail": "Saudi Arabia", "Taif": "Saudi Arabia", - - // Argentina - "Buenos Aires": "Argentina", "Rosario": "Argentina", "Mendoza": "Argentina", - "Tucumán": "Argentina", "La Plata": "Argentina", "Mar del Plata": "Argentina", "Salta": "Argentina", - "Santa Fe": "Argentina", "San Juan": "Argentina", "Resistencia": "Argentina", "Santiago del Estero": "Argentina", - - // Poland - "Warsaw": "Poland", "Kraków": "Poland", "Łódź": "Poland", "Wrocław": "Poland", - "Poznań": "Poland", "Gdańsk": "Poland", "Szczecin": "Poland", "Bydgoszcz": "Poland", - "Lublin": "Poland", "Katowice": "Poland", "Białystok": "Poland", "Gdynia": "Poland", - - // Ukraine - "Kiev": "Ukraine", "Kharkiv": "Ukraine", "Odessa": "Ukraine", "Dnipro": "Ukraine", - "Donetsk": "Ukraine", "Zaporizhzhia": "Ukraine", "Lviv": "Ukraine", "Kryvyi Rih": "Ukraine", - "Mykolaiv": "Ukraine", "Mariupol": "Ukraine", "Luhansk": "Ukraine", "Vinnytsia": "Ukraine", - - // Egypt - "Cairo": "Egypt", "Alexandria": "Egypt", "Giza": "Egypt", "Shubra El Kheima": "Egypt", - "Port Said": "Egypt", "Suez": "Egypt", "Luxor": "Egypt", "Mansoura": "Egypt", - "El Mahalla El Kubra": "Egypt", "Tanta": "Egypt", "Asyut": "Egypt", "Ismailia": "Egypt", - - // Nigeria - "Lagos": "Nigeria", "Kano": "Nigeria", "Ibadan": "Nigeria", "Abuja": "Nigeria", - "Port Harcourt": "Nigeria", "Benin City": "Nigeria", "Maiduguri": "Nigeria", "Zaria": "Nigeria", - "Aba": "Nigeria", "Jos": "Nigeria", "Ilorin": "Nigeria", "Oyo": "Nigeria", - - // South Africa - "Johannesburg": "South Africa", "Cape Town": "South Africa", "Durban": "South Africa", "Pretoria": "South Africa", - "Soweto": "South Africa", "Port Elizabeth": "South Africa", "Pietermaritzburg": "South Africa", "Benoni": "South Africa", - "Tembisa": "South Africa", "East London": "South Africa", "Vereeniging": "South Africa", "Bloemfontein": "South Africa", - - // Iran - "Tehran": "Iran", "Mashhad": "Iran", "Isfahan": "Iran", "Karaj": "Iran", - "Shiraz": "Iran", "Tabriz": "Iran", "Qom": "Iran", "Ahvaz": "Iran", - "Kermanshah": "Iran", "Urmia": "Iran", "Rasht": "Iran", "Zahedan": "Iran", - - // Thailand - "Bangkok": "Thailand", "Nonthaburi": "Thailand", "Pak Kret": "Thailand", "Hat Yai": "Thailand", - "Chiang Mai": "Thailand", "Phuket": "Thailand", "Pattaya": "Thailand", "Nakhon Ratchasima": "Thailand", - "Khon Kaen": "Thailand", "Udon Thani": "Thailand", "Surat Thani": "Thailand", "Nakhon Si Thammarat": "Thailand", - - // Vietnam - "Ho Chi Minh City": "Vietnam", "Hanoi": "Vietnam", "Haiphong": "Vietnam", "Da Nang": "Vietnam", - "Bien Hoa": "Vietnam", "Hue": "Vietnam", "Nha Trang": "Vietnam", "Can Tho": "Vietnam", - "Rach Gia": "Vietnam", "Qui Nhon": "Vietnam", "Vung Tau": "Vietnam", "Nam Dinh": "Vietnam", - - // Philippines - "Manila": "Philippines", "Quezon City": "Philippines", "Davao": "Philippines", "Caloocan": "Philippines", - "Cebu City": "Philippines", "Zamboanga": "Philippines", "Antipolo": "Philippines", "Taguig": "Philippines", - "Pasig": "Philippines", "Cagayan de Oro": "Philippines", "Paranaque": "Philippines", "Makati": "Philippines", - - // Malaysia - "Kuala Lumpur": "Malaysia", "George Town": "Malaysia", "Ipoh": "Malaysia", "Shah Alam": "Malaysia", - "Petaling Jaya": "Malaysia", "Johor Bahru": "Malaysia", "Seremban": "Malaysia", "Kuching": "Malaysia", - "Kota Kinabalu": "Malaysia", "Klang": "Malaysia", "Kajang": "Malaysia", "Subang Jaya": "Malaysia", - - // Bangladesh - "Dhaka": "Bangladesh", "Chittagong": "Bangladesh", "Sylhet": "Bangladesh", "Khulna": "Bangladesh", - "Rajshahi": "Bangladesh", "Rangpur": "Bangladesh", "Barisal": "Bangladesh", "Comilla": "Bangladesh", - "Mymensingh": "Bangladesh", "Narayanganj": "Bangladesh", "Gazipur": "Bangladesh", "Tongi": "Bangladesh", - - // Pakistan - "Karachi": "Pakistan", "Lahore": "Pakistan", "Faisalabad": "Pakistan", "Rawalpindi": "Pakistan", - "Gujranwala": "Pakistan", "Peshawar": "Pakistan", "Multan": "Pakistan", "Islamabad": "Pakistan", - "Quetta": "Pakistan", "Bahawalpur": "Pakistan", "Sargodha": "Pakistan", "Sialkot": "Pakistan", - - // Afghanistan - "Kabul": "Afghanistan", "Kandahar": "Afghanistan", "Herat": "Afghanistan", "Mazar-i-Sharif": "Afghanistan", - "Jalalabad": "Afghanistan", "Kunduz": "Afghanistan", "Ghazni": "Afghanistan", "Bamyan": "Afghanistan", - - // Israel - "Jerusalem": "Israel", "Tel Aviv": "Israel", "Haifa": "Israel", "Rishon LeZion": "Israel", - "Petah Tikva": "Israel", "Ashdod": "Israel", "Netanya": "Israel", "Beer Sheva": "Israel", - "Holon": "Israel", "Bnei Brak": "Israel", "Ramat Gan": "Israel", "Ashkelon": "Israel", - - // Iraq - "Baghdad": "Iraq", "Basra": "Iraq", "Mosul": "Iraq", "Erbil": "Iraq", - "Sulaymaniyah": "Iraq", "Najaf": "Iraq", "Karbala": "Iraq", "Nasiriyah": "Iraq", - "Amarah": "Iraq", "Duhok": "Iraq", "Ramadi": "Iraq", "Fallujah": "Iraq", - - // Morocco - "Casablanca": "Morocco", "Rabat": "Morocco", "Fes": "Morocco", "Marrakech": "Morocco", - "Tangier": "Morocco", "Meknes": "Morocco", "Oujda": "Morocco", "Kenitra": "Morocco", - "Tetouan": "Morocco", "Safi": "Morocco", "El Jadida": "Morocco", "Nador": "Morocco", - - // Algeria - "Algiers": "Algeria", "Oran": "Algeria", "Constantine": "Algeria", "Batna": "Algeria", - "Djelfa": "Algeria", "Setif": "Algeria", "Annaba": "Algeria", "Sidi Bel Abbes": "Algeria", - "Biskra": "Algeria", "Tebessa": "Algeria", "El Oued": "Algeria", "Skikda": "Algeria", - - // Kenya - "Nairobi": "Kenya", "Mombasa": "Kenya", "Kisumu": "Kenya", "Nakuru": "Kenya", - "Eldoret": "Kenya", "Kitale": "Kenya", "Malindi": "Kenya", "Garissa": "Kenya", - "Kakamega": "Kenya", "Nyeri": "Kenya", "Machakos": "Kenya", "Meru": "Kenya", - - // Ethiopia - "Addis Ababa": "Ethiopia", "Dire Dawa": "Ethiopia", "Mek'ele": "Ethiopia", "Gondar": "Ethiopia", - "Adama": "Ethiopia", "Awasa": "Ethiopia", "Bahir Dar": "Ethiopia", "Dessie": "Ethiopia", - "Jimma": "Ethiopia", "Jijiga": "Ethiopia", "Shashamane": "Ethiopia", "Nekemte": "Ethiopia", - - // Ghana - "Accra": "Ghana", "Kumasi": "Ghana", "Tamale": "Ghana", "Sekondi-Takoradi": "Ghana", - "Ashaiman": "Ghana", "Cape Coast": "Ghana", "Obuasi": "Ghana", "Teshie": "Ghana", - "Madina": "Ghana", "Koforidua": "Ghana", "Wa": "Ghana", "Techiman": "Ghana", - - // Chile - "Santiago": "Chile", "Valparaíso": "Chile", "Concepción": "Chile", "La Serena": "Chile", - "Antofagasta": "Chile", "Temuco": "Chile", "Rancagua": "Chile", "Talca": "Chile", - "Arica": "Chile", "Chillán": "Chile", "Iquique": "Chile", - - // Colombia - "Bogotá": "Colombia", "Medellín": "Colombia", "Cali": "Colombia", "Barranquilla": "Colombia", - "Cartagena": "Colombia", "Cúcuta": "Colombia", "Bucaramanga": "Colombia", "Pereira": "Colombia", - "Santa Marta": "Colombia", "Ibagué": "Colombia", "Pasto": "Colombia", "Manizales": "Colombia", - - // Peru - "Lima": "Peru", "Arequipa": "Peru", "Trujillo": "Peru", "Chiclayo": "Peru", - "Piura": "Peru", "Iquitos": "Peru", "Cusco": "Peru", "Chimbote": "Peru", - "Huancayo": "Peru", "Tacna": "Peru", "Juliaca": "Peru", "Ica": "Peru", - - // Venezuela - "Caracas": "Venezuela", "Maracaibo": "Venezuela", "Barquisimeto": "Venezuela", - "Maracay": "Venezuela", "Ciudad Guayana": "Venezuela", "San Cristóbal": "Venezuela", "Maturín": "Venezuela", - "Ciudad Bolívar": "Venezuela", "Cumana": "Venezuela", - - // Ecuador - "Guayaquil": "Ecuador", "Quito": "Ecuador", "Cuenca": "Ecuador", "Santo Domingo": "Ecuador", - "Machala": "Ecuador", "Durán": "Ecuador", "Manta": "Ecuador", "Portoviejo": "Ecuador", - "Ambato": "Ecuador", "Riobamba": "Ecuador", "Loja": "Ecuador", "Esmeraldas": "Ecuador", - - // Bolivia - "Santa Cruz": "Bolivia", "La Paz": "Bolivia", "Cochabamba": "Bolivia", "Oruro": "Bolivia", - "Sucre": "Bolivia", "Tarija": "Bolivia", "Potosí": "Bolivia", "Trinidad": "Bolivia", - - // Uruguay - "Montevideo": "Uruguay", "Salto": "Uruguay", "Paysandú": "Uruguay", "Las Piedras": "Uruguay", - "Rivera": "Uruguay", "Maldonado": "Uruguay", "Tacuarembó": "Uruguay", "Melo": "Uruguay", - - // Paraguay - "Asunción": "Paraguay", "Ciudad del Este": "Paraguay", "San Lorenzo": "Paraguay", "Luque": "Paraguay", - "Capiatá": "Paraguay", "Lambaré": "Paraguay", "Fernando de la Mora": "Paraguay", "Limpio": "Paraguay", - - // Norway - "Oslo": "Norway", "Bergen": "Norway", "Trondheim": "Norway", "Stavanger": "Norway", - "Bærum": "Norway", "Kristiansand": "Norway", "Fredrikstad": "Norway", "Sandnes": "Norway", - "Tromsø": "Norway", "Drammen": "Norway", "Asker": "Norway", "Lillestrøm": "Norway", - - // Sweden - "Stockholm": "Sweden", "Gothenburg": "Sweden", "Malmö": "Sweden", "Uppsala": "Sweden", - "Västerås": "Sweden", "Örebro": "Sweden", "Linköping": "Sweden", "Helsingborg": "Sweden", - "Jönköping": "Sweden", "Norrköping": "Sweden", "Lund": "Sweden", "Umeå": "Sweden", - - // Finland - "Helsinki": "Finland", "Espoo": "Finland", "Tampere": "Finland", "Vantaa": "Finland", - "Oulu": "Finland", "Turku": "Finland", "Jyväskylä": "Finland", "Lahti": "Finland", - "Kuopio": "Finland", "Pori": "Finland", "Joensuu": "Finland", "Lappeenranta": "Finland", - - // Denmark - "Copenhagen": "Denmark", "Aarhus": "Denmark", "Odense": "Denmark", "Aalborg": "Denmark", - "Esbjerg": "Denmark", "Randers": "Denmark", "Kolding": "Denmark", "Horsens": "Denmark", - "Vejle": "Denmark", "Roskilde": "Denmark", "Herning": "Denmark", "Silkeborg": "Denmark", - - // Switzerland - "Zurich": "Switzerland", "Geneva": "Switzerland", "Basel": "Switzerland", "Bern": "Switzerland", - "Lausanne": "Switzerland", "Winterthur": "Switzerland", "Lucerne": "Switzerland", "St. Gallen": "Switzerland", - "Lugano": "Switzerland", "Biel": "Switzerland", "Thun": "Switzerland", "Köniz": "Switzerland", - - // Austria - "Innsbruck": "Austria", "Klagenfurt": "Austria", "Villach": "Austria", "Wels": "Austria", - "Sankt Pölten": "Austria", "Dornbirn": "Austria", "Steyr": "Austria", "Wiener Neustadt": "Austria", - "Feldkirch": "Austria", "Bregenz": "Austria", "Leonding": "Austria", "Klosterneuburg": "Austria", - - // Belgium - "Brussels": "Belgium", "Antwerp": "Belgium", "Ghent": "Belgium", "Charleroi": "Belgium", - "Liège": "Belgium", "Bruges": "Belgium", "Namur": "Belgium", "Leuven": "Belgium", - "Mons": "Belgium", "Aalst": "Belgium", "Mechelen": "Belgium", "La Louvière": "Belgium", - - // Czech Republic (Czechia) - "Prague": "Czechia", "Brno": "Czechia", "Ostrava": "Czechia", "Plzen": "Czechia", - "Liberec": "Czechia", "Olomouc": "Czechia", "Budweis": "Czechia", "Hradec Králové": "Czechia", - "Ústí nad Labem": "Czechia", "Pardubice": "Czechia", "Zlín": "Czechia", "Havířov": "Czechia", - - // Hungary - "Budapest": "Hungary", "Debrecen": "Hungary", "Szeged": "Hungary", "Miskolc": "Hungary", - "Pécs": "Hungary", "Győr": "Hungary", "Nyíregyháza": "Hungary", "Kecskemét": "Hungary", - "Székesfehérvár": "Hungary", "Szombathely": "Hungary", "Érd": "Hungary", "Tatabánya": "Hungary", - - // Romania - "Bucharest": "Romania", "Cluj-Napoca": "Romania", "Timișoara": "Romania", "Iași": "Romania", - "Constanța": "Romania", "Craiova": "Romania", "Brașov": "Romania", "Galați": "Romania", - "Ploiești": "Romania", "Oradea": "Romania", "Brăila": "Romania", "Arad": "Romania", - - // Serbia - "Belgrade": "Serbia", "Novi Sad": "Serbia", "Niš": "Serbia", "Kragujevac": "Serbia", - "Subotica": "Serbia", "Zrenjanin": "Serbia", "Pančevo": "Serbia", "Čačak": "Serbia", - "Novi Pazar": "Serbia", "Kraljevo": "Serbia", "Smederevo": "Serbia", "Leskovac": "Serbia", - - // Croatia - "Zagreb": "Croatia", "Split": "Croatia", "Rijeka": "Croatia", "Osijek": "Croatia", - "Zadar": "Croatia", "Pula": "Croatia", "Slavonski Brod": "Croatia", "Karlovac": "Croatia", - "Varaždin": "Croatia", "Šibenik": "Croatia", "Sisak": "Croatia", "Velika Gorica": "Croatia", - - // Greece - "Athens": "Greece", "Thessaloniki": "Greece", "Patras": "Greece", "Heraklion": "Greece", - "Larissa": "Greece", "Volos": "Greece", "Rhodes": "Greece", "Ioannina": "Greece", - "Chania": "Greece", "Chalcis": "Greece", "Serres": "Greece", "Alexandroupoli": "Greece", - - // Portugal - "Lisbon": "Portugal", "Porto": "Portugal", "Vila Nova de Gaia": "Portugal", "Amadora": "Portugal", - "Braga": "Portugal", "Funchal": "Portugal", "Coimbra": "Portugal", "Setúbal": "Portugal", - "Almada": "Portugal", "Agualva-Cacém": "Portugal", "Queluz": "Portugal", "Rio Tinto": "Portugal", - - // Bulgaria - "Sofia": "Bulgaria", "Plovdiv": "Bulgaria", "Varna": "Bulgaria", "Burgas": "Bulgaria", - "Ruse": "Bulgaria", "Stara Zagora": "Bulgaria", "Pleven": "Bulgaria", "Sliven": "Bulgaria", - "Dobrich": "Bulgaria", "Shumen": "Bulgaria", "Pernik": "Bulgaria", "Haskovo": "Bulgaria", - - // Slovakia - "Bratislava": "Slovakia", "Košice": "Slovakia", "Prešov": "Slovakia", "Žilina": "Slovakia", - "Banská Bystrica": "Slovakia", "Nitra": "Slovakia", "Trnava": "Slovakia", "Martin": "Slovakia", - "Trenčín": "Slovakia", "Poprad": "Slovakia", "Prievidza": "Slovakia", "Zvolen": "Slovakia", - - // Slovenia - "Ljubljana": "Slovenia", "Maribor": "Slovenia", "Celje": "Slovenia", "Kranj": "Slovenia", - "Velenje": "Slovenia", "Koper": "Slovenia", "Novo Mesto": "Slovenia", "Ptuj": "Slovenia", - "Trbovlje": "Slovenia", "Kamnik": "Slovenia", "Jesenice": "Slovenia", "Nova Gorica": "Slovenia", - - // Lithuania - "Vilnius": "Lithuania", "Kaunas": "Lithuania", "Klaipėda": "Lithuania", "Šiauliai": "Lithuania", - "Panevėžys": "Lithuania", "Alytus": "Lithuania", "Marijampolė": "Lithuania", "Mažeikiai": "Lithuania", - "Jonava": "Lithuania", "Utena": "Lithuania", "Kėdainiai": "Lithuania", "Telšiai": "Lithuania", - - // Latvia - "Riga": "Latvia", "Daugavpils": "Latvia", "Liepāja": "Latvia", "Jelgava": "Latvia", - "Jūrmala": "Latvia", "Ventspils": "Latvia", "Rēzekne": "Latvia", "Valmiera": "Latvia", - "Jēkabpils": "Latvia", "Ogre": "Latvia", "Tukums": "Latvia", "Salaspils": "Latvia", - - // Estonia - "Tallinn": "Estonia", "Tartu": "Estonia", "Narva": "Estonia", "Pärnu": "Estonia", - "Kohtla-Järve": "Estonia", "Viljandi": "Estonia", "Rakvere": "Estonia", "Maardu": "Estonia", - "Sillamäe": "Estonia", "Kuressaare": "Estonia", "Võru": "Estonia", "Valga": "Estonia", - - // New Zealand - "Auckland": "New Zealand", "Wellington": "New Zealand", "Christchurch": "New Zealand", "Hamilton": "New Zealand", - "Tauranga": "New Zealand", "Napier-Hastings": "New Zealand", "Dunedin": "New Zealand", "Palmerston North": "New Zealand", - "Nelson": "New Zealand", "Rotorua": "New Zealand", "New Plymouth": "New Zealand", "Whangarei": "New Zealand", - - // Singapore - "Singapore": "Singapore", - - // United Arab Emirates - "Dubai": "United Arab Emirates", "Abu Dhabi": "United Arab Emirates", "Sharjah": "United Arab Emirates", "Al Ain": "United Arab Emirates", - "Ajman": "United Arab Emirates", "Ras Al Khaimah": "United Arab Emirates", "Fujairah": "United Arab Emirates", "Umm Al Quwain": "United Arab Emirates", - - // Lebanon - "Beirut": "Lebanon", "Sidon": "Lebanon", "Tyre": "Lebanon", - "Nabatieh": "Lebanon", "Jounieh": "Lebanon", "Zahle": "Lebanon", "Baalbek": "Lebanon", - - // Jordan - "Amman": "Jordan", "Zarqa": "Jordan", "Irbid": "Jordan", "Russeifa": "Jordan", - "Wadi as-Sir": "Jordan", "Aqaba": "Jordan", "Madaba": "Jordan", "Salt": "Jordan", - - // Yemen - "Sanaa": "Yemen", "Aden": "Yemen", "Taiz": "Yemen", "Hodeidah": "Yemen", - "Mukalla": "Yemen", "Ibb": "Yemen", "Dhamar": "Yemen", "Zinjibar": "Yemen", - - // Syria - "Damascus": "Syria", "Aleppo": "Syria", "Homs": "Syria", "Latakia": "Syria", - "Hama": "Syria", "Raqqa": "Syria", "Deir ez-Zor": "Syria", "Hasakah": "Syria", - - // Oman - "Muscat": "Oman", "Seeb": "Oman", "Salalah": "Oman", "Bawshar": "Oman", - "Sohar": "Oman", "Sur": "Oman", "Ibra": "Oman", "Nizwa": "Oman", - - // Qatar - "Doha": "Qatar", "Al Rayyan": "Qatar", "Umm Salal": "Qatar", "Al Wakrah": "Qatar", - "Al Khor": "Qatar", "Dukhan": "Qatar", "Lusail": "Qatar", "Mesaieed": "Qatar", - - // Kuwait - "Kuwait City": "Kuwait", "Al Ahmadi": "Kuwait", "Hawally": "Kuwait", "As Salimiyah": "Kuwait", - "Sabah as-Salim": "Kuwait", "Al Farwaniyah": "Kuwait", "Al Fahahil": "Kuwait", "Ar Riqqah": "Kuwait", - - // Bahrain - "Manama": "Bahrain", "Riffa": "Bahrain", "Muharraq": "Bahrain", "Hamad Town": "Bahrain", - "A'ali": "Bahrain", "Isa Town": "Bahrain", "Sitra": "Bahrain", "Budaiya": "Bahrain", - - // Cyprus - "Nicosia": "Cyprus", "Limassol": "Cyprus", "Larnaca": "Cyprus", "Famagusta": "Cyprus", - "Paphos": "Cyprus", "Kyrenia": "Cyprus", "Protaras": "Cyprus", "Paralimni": "Cyprus", - - // Malta - "Valletta": "Malta", "Birkirkara": "Malta", "Mosta": "Malta", "Qormi": "Malta", - "Zabbar": "Malta", "San Pawl il-Bahar": "Malta", "Tarxien": "Malta", "Naxxar": "Malta", - - // Iceland - "Reykjavik": "Iceland", "Kopavogur": "Iceland", "Hafnarfjordur": "Iceland", "Akureyri": "Iceland", - "Reykjanesbaer": "Iceland", "Gardabaer": "Iceland", "Mosfellsbaer": "Iceland", "Arborg": "Iceland", - - // Luxembourg - "Luxembourg City": "Luxembourg", "Esch-sur-Alzette": "Luxembourg", "Dudelange": "Luxembourg", "Schifflange": "Luxembourg", - "Bettembourg": "Luxembourg", "Petange": "Luxembourg", "Ettelbruck": "Luxembourg", "Diekirch": "Luxembourg", - - // Belarus - "Minsk": "Belarus", "Gomel": "Belarus", "Mogilev": "Belarus", "Vitebsk": "Belarus", - "Grodno": "Belarus", "Brest": "Belarus", "Bobruisk": "Belarus", "Baranovichi": "Belarus", - - // Moldova - "Chisinau": "Moldova", "Tiraspol": "Moldova", "Balti": "Moldova", "Bender": "Moldova", - "Rybnitsa": "Moldova", "Cahul": "Moldova", "Ungheni": "Moldova", "Soroca": "Moldova", - - // North Macedonia - "Skopje": "North Macedonia", "Bitola": "North Macedonia", "Kumanovo": "North Macedonia", "Prilep": "North Macedonia", - "Tetovo": "North Macedonia", "Veles": "North Macedonia", "Shtip": "North Macedonia", "Ohrid": "North Macedonia", - - // Albania - "Tirana": "Albania", "Durres": "Albania", "Vlore": "Albania", "Elbasan": "Albania", - "Shkoder": "Albania", "Fier": "Albania", "Korce": "Albania", "Berat": "Albania", - - // Montenegro - "Podgorica": "Montenegro", "Niksic": "Montenegro", "Pljevlja": "Montenegro", "Bijelo Polje": "Montenegro", - "Cetinje": "Montenegro", "Bar": "Montenegro", "Herceg Novi": "Montenegro", "Berane": "Montenegro", - - // Bosnia and Herzegovina - "Sarajevo": "Bosnia and Herzegovina", "Banja Luka": "Bosnia and Herzegovina", "Tuzla": "Bosnia and Herzegovina", "Zenica": "Bosnia and Herzegovina", - "Mostar": "Bosnia and Herzegovina", "Prijedor": "Bosnia and Herzegovina", "Brčko": "Bosnia and Herzegovina", "Bijeljina": "Bosnia and Herzegovina", - - // Armenia - "Yerevan": "Armenia", "Gyumri": "Armenia", "Vanadzor": "Armenia", "Vagharshapat": "Armenia", - "Hrazdan": "Armenia", "Abovyan": "Armenia", "Kapan": "Armenia", "Armavir": "Armenia", - - // Georgia - "Tbilisi": "Georgia", "Kutaisi": "Georgia", "Batumi": "Georgia", "Rustavi": "Georgia", - "Gori": "Georgia", "Zugdidi": "Georgia", "Poti": "Georgia", "Kobuleti": "Georgia", - - // Azerbaijan - "Baku": "Azerbaijan", "Ganja": "Azerbaijan", "Sumqayit": "Azerbaijan", "Mingachevir": "Azerbaijan", - "Quba": "Azerbaijan", "Lankaran": "Azerbaijan", "Shaki": "Azerbaijan", "Yevlax": "Azerbaijan", - - // Kazakhstan - "Almaty": "Kazakhstan", "Nur-Sultan": "Kazakhstan", "Shymkent": "Kazakhstan", "Aktobe": "Kazakhstan", - "Taraz": "Kazakhstan", "Pavlodar": "Kazakhstan", "Ust-Kamenogorsk": "Kazakhstan", "Semey": "Kazakhstan", - "Atyrau": "Kazakhstan", "Kostanay": "Kazakhstan", "Kyzylorda": "Kazakhstan", "Oral": "Kazakhstan", - - // Uzbekistan - "Tashkent": "Uzbekistan", "Namangan": "Uzbekistan", "Samarkand": "Uzbekistan", "Andijan": "Uzbekistan", - "Nukus": "Uzbekistan", "Fergana": "Uzbekistan", "Bukhara": "Uzbekistan", "Qarshi": "Uzbekistan", - "Kokand": "Uzbekistan", "Margilan": "Uzbekistan", "Chirchiq": "Uzbekistan", "Termez": "Uzbekistan", - - // Kyrgyzstan - "Bishkek": "Kyrgyzstan", "Osh": "Kyrgyzstan", "Jalal-Abad": "Kyrgyzstan", "Karakol": "Kyrgyzstan", - "Tokmok": "Kyrgyzstan", "Uzgen": "Kyrgyzstan", "Naryn": "Kyrgyzstan", "Talas": "Kyrgyzstan", - - // Tajikistan - "Dushanbe": "Tajikistan", "Khujand": "Tajikistan", "Kulob": "Tajikistan", "Qurghonteppa": "Tajikistan", - "Istaravshan": "Tajikistan", "Isfara": "Tajikistan", "Panjakent": "Tajikistan", "Tursunzoda": "Tajikistan", - - // Turkmenistan - "Ashgabat": "Turkmenistan", "Turkmenbashi": "Turkmenistan", "Dasoguz": "Turkmenistan", "Mary": "Turkmenistan", - "Balkanabat": "Turkmenistan", "Bayramaly": "Turkmenistan", "Tejen": "Turkmenistan", "Serdar": "Turkmenistan", - - // Mongolia - "Ulaanbaatar": "Mongolia", "Erdenet": "Mongolia", "Darkhan": "Mongolia", "Choibalsan": "Mongolia", - "Murun": "Mongolia", "Bayankhongor": "Mongolia", "Ulgii": "Mongolia", "Khovd": "Mongolia", - - // Myanmar - "Yangon": "Myanmar", "Mandalay": "Myanmar", "Naypyidaw": "Myanmar", "Mawlamyine": "Myanmar", - "Bago": "Myanmar", "Pathein": "Myanmar", "Monywa": "Myanmar", "Meiktila": "Myanmar", - "Sittwe": "Myanmar", "Myitkyina": "Myanmar", "Dawei": "Myanmar", "Pyay": "Myanmar", - - // Laos - "Vientiane": "Laos", "Pakse": "Laos", "Savannakhet": "Laos", "Luang Prabang": "Laos", - "Thakhek": "Laos", "Muang Xay": "Laos", "Phonsavan": "Laos", "Muang Pakbeng": "Laos", - - // Cambodia - "Phnom Penh": "Cambodia", "Siem Reap": "Cambodia", "Battambang": "Cambodia", "Sihanoukville": "Cambodia", - "Poipet": "Cambodia", "Kampong Cham": "Cambodia", "Pursat": "Cambodia", "Kampong Speu": "Cambodia", - - // Sri Lanka - "Colombo": "Sri Lanka", "Dehiwala-Mount Lavinia": "Sri Lanka", "Moratuwa": "Sri Lanka", "Negombo": "Sri Lanka", - "Kandy": "Sri Lanka", "Kalmunai": "Sri Lanka", "Galle": "Sri Lanka", "Trincomalee": "Sri Lanka", - "Batticaloa": "Sri Lanka", "Jaffna": "Sri Lanka", "Katunayake": "Sri Lanka", "Dambulla": "Sri Lanka", - - // Nepal - "Kathmandu": "Nepal", "Pokhara": "Nepal", "Lalitpur": "Nepal", "Bharatpur": "Nepal", - "Biratnagar": "Nepal", "Birgunj": "Nepal", "Dharan": "Nepal", "Bhim Datta": "Nepal", - "Butwal": "Nepal", "Hetauda": "Nepal", "Dhangadhi": "Nepal", "Itahari": "Nepal", - - // Bhutan - "Thimphu": "Bhutan", "Phuntsholing": "Bhutan", "Punakha": "Bhutan", "Wangdue": "Bhutan", - "Samdrup Jongkhar": "Bhutan", "Gelephu": "Bhutan", "Trongsa": "Bhutan", "Mongar": "Bhutan", - - // Maldives - "Male": "Maldives", "Addu City": "Maldives", "Fuvahmulah": "Maldives", "Kulhudhuffushi": "Maldives", - "Thinadhoo": "Maldives", "Ungoofaaru": "Maldives", "Naifaru": "Maldives", "Dhidhdhoo": "Maldives", - - // Madagascar - "Antananarivo": "Madagascar", "Toamasina": "Madagascar", "Antsirabe": "Madagascar", "Fianarantsoa": "Madagascar", - "Mahajanga": "Madagascar", "Toliara": "Madagascar", "Antsiranana": "Madagascar", "Ambovombe": "Madagascar", - "Morondava": "Madagascar", "Sambava": "Madagascar", "Manakara": "Madagascar", "Farafangana": "Madagascar", - - // Mauritius - "Port Louis": "Mauritius", "Beau Bassin-Rose Hill": "Mauritius", "Vacoas-Phoenix": "Mauritius", "Curepipe": "Mauritius", - "Quatre Bornes": "Mauritius", "Triolet": "Mauritius", "Goodlands": "Mauritius", "Centre de Flacq": "Mauritius", - - // Seychelles - "Victoria": "Seychelles", "Anse Boileau": "Seychelles", "Beau Vallon": "Seychelles", "Cascade": "Seychelles", - "Anse Royale": "Seychelles", "Takamaka": "Seychelles", "Port Glaud": "Seychelles", "Grand Anse Mahe": "Seychelles", - - // Comoros - "Moroni": "Comoros", "Mutsamudu": "Comoros", "Fomboni": "Comoros", "Domoni": "Comoros", - "Sima": "Comoros", "Mitsoudje": "Comoros", "Adda-Doueni": "Comoros", "Ouani": "Comoros", - - // Djibouti - "Djibouti City": "Djibouti", "Ali Sabieh": "Djibouti", "Dikhil": "Djibouti", "Tadjoura": "Djibouti", - "Obock": "Djibouti", "Arta": "Djibouti", "Holhol": "Djibouti", "Yoboki": "Djibouti", - - // Eritrea - "Asmara": "Eritrea", "Assab": "Eritrea", "Massawa": "Eritrea", "Keren": "Eritrea", - "Mendefera": "Eritrea", "Barentu": "Eritrea", "Adi Keih": "Eritrea", "Adi Quala": "Eritrea", - - // Somalia - "Mogadishu": "Somalia", "Hargeisa": "Somalia", "Bosaso": "Somalia", "Kismayo": "Somalia", - "Merca": "Somalia", "Galcayo": "Somalia", "Berbera": "Somalia", "Baidoa": "Somalia", - "Garowe": "Somalia", "Jowhar": "Somalia", "Borama": "Somalia", "Las Anod": "Somalia", - - // Sudan - "Khartoum": "Sudan", "Omdurman": "Sudan", "Port Sudan": "Sudan", "Kassala": "Sudan", - "Obeid": "Sudan", "Nyala": "Sudan", "Gedaref": "Sudan", "Wad Medani": "Sudan", - "El Fasher": "Sudan", "Kosti": "Sudan", "Sennar": "Sudan", "Dongola": "Sudan", - - // South Sudan - "Juba": "South Sudan", "Malakal": "South Sudan", "Wau": "South Sudan", "Bentiu": "South Sudan", - "Yei": "South Sudan", "Aweil": "South Sudan", "Kuacjok": "South Sudan", "Bor": "South Sudan", - - // Chad - "N'Djamena": "Chad", "Moundou": "Chad", "Sarh": "Chad", "Abéché": "Chad", - "Kelo": "Chad", "Koumra": "Chad", "Pala": "Chad", "Am Timan": "Chad", - - // Central African Republic - "Bangui": "Central African Republic", "Bimbo": "Central African Republic", "Berbérati": "Central African Republic", "Carnot": "Central African Republic", - "Bambari": "Central African Republic", "Bouar": "Central African Republic", "Bossangoa": "Central African Republic", "Bria": "Central African Republic", - - // Democratic Republic of Congo - "Kinshasa": "Congo", "Lubumbashi": "Congo", "Mbuji-Mayi": "Congo", "Kisangani": "Congo", - "Masina": "Congo", "Kananga": "Congo", "Likasi": "Congo", "Kolwezi": "Congo", - "Tshikapa": "Congo", "Beni": "Congo", "Bukavu": "Congo", "Mwene-Ditu": "Congo", - - // Republic of Congo - "Brazzaville": "Congo", "Pointe-Noire": "Congo", "Dolisie": "Congo", "Nkayi": "Congo", - "Impfondo": "Congo", "Ouesso": "Congo", "Madingou": "Congo", "Owando": "Congo", - - // Gabon - "Libreville": "Gabon", "Port-Gentil": "Gabon", "Franceville": "Gabon", "Oyem": "Gabon", - "Moanda": "Gabon", "Mouila": "Gabon", "Lambaréné": "Gabon", "Tchibanga": "Gabon", - - // Equatorial Guinea - "Malabo": "Equatorial Guinea", "Bata": "Equatorial Guinea", "Ebebiyin": "Equatorial Guinea", "Aconibe": "Equatorial Guinea", - "Añisoc": "Equatorial Guinea", "Luba": "Equatorial Guinea", "Evinayong": "Equatorial Guinea", "Mengomeyén": "Equatorial Guinea", - - // Cameroon - "Yaoundé": "Cameroon", "Douala": "Cameroon", "Bamenda": "Cameroon", "Bafoussam": "Cameroon", - "Garoua": "Cameroon", "Maroua": "Cameroon", "Nkongsamba": "Cameroon", "Bertoua": "Cameroon", - "Edéa": "Cameroon", "Loum": "Cameroon", "Kumba": "Cameroon", "Foumban": "Cameroon", - - // Angola - "Luanda": "Angola", "Huambo": "Angola", "Lobito": "Angola", "Benguela": "Angola", - "Kuito": "Angola", "Lubango": "Angola", "Malanje": "Angola", "Namibe": "Angola", - "Soyo": "Angola", "Cabinda": "Angola", "Uíge": "Angola", "Saurimo": "Angola", - - // Zambia - "Lusaka": "Zambia", "Kitwe": "Zambia", "Ndola": "Zambia", "Kabwe": "Zambia", - "Chingola": "Zambia", "Mufulira": "Zambia", "Livingstone": "Zambia", "Luanshya": "Zambia", - "Kasama": "Zambia", "Chipata": "Zambia", "Mazabuka": "Zambia", "Mongu": "Zambia", - - // Zimbabwe - "Harare": "Zimbabwe", "Bulawayo": "Zimbabwe", "Chitungwiza": "Zimbabwe", "Mutare": "Zimbabwe", - // Namibia - "Windhoek": "Namibia", "Rundu": "Namibia", "Walvis Bay": "Namibia", "Oshakati": "Namibia", - "Swakopmund": "Namibia", "Katima Mulilo": "Namibia", "Grootfontein": "Namibia", "Rehoboth": "Namibia", - "Otjiwarongo": "Namibia", "Okahandja": "Namibia", "Ondangwa": "Namibia", "Outapi": "Namibia", - "Conakry": "Guinea", "Nzérékoré": "Guinea", "Kankan": "Guinea", "Kindia": "Guinea", - // Botswana - "Gaborone": "Botswana", "Francistown": "Botswana", "Molepolole": "Botswana", "Maun": "Botswana", - "Serowe": "Botswana", "Selibe Phikwe": "Botswana", "Kanye": "Botswana", "Mochudi": "Botswana", - "Mahalapye": "Botswana", "Palapye": "Botswana", "Lobatse": "Botswana", "Kasane": "Botswana", - // Guinea-Bissau (补充缺失) - // Lesotho - "Maseru": "Lesotho", "Teyateyaneng": "Lesotho", "Mafeteng": "Lesotho", "Hlotse": "Lesotho", - "Mohale's Hoek": "Lesotho", "Maputsoe": "Lesotho", "Qacha's Nek": "Lesotho", "Quthing": "Lesotho", - "Freetown": "Sierra Leone", "Bo": "Sierra Leone", "Kenema": "Sierra Leone", "Koidu": "Sierra Leone", - // Eswatini (Swaziland) - "Mbabane": "Eswatini", "Manzini": "Eswatini", "Big Bend": "Eswatini", "Malkerns": "Eswatini", - "Nhlangano": "Eswatini", "Siteki": "Eswatini", "Pigg's Peak": "Eswatini", "Lobamba": "Eswatini", - "Monrovia": "Liberia", "Gbarnga": "Liberia", "Kakata": "Liberia", "Bensonville": "Liberia", - // Malawi - "Lilongwe": "Malawi", "Blantyre": "Malawi", "Mzuzu": "Malawi", "Zomba": "Malawi", - "Kasungu": "Malawi", "Mangochi": "Malawi", "Karonga": "Malawi", "Salima": "Malawi", - "Liwonde": "Malawi", "Nkhotakota": "Malawi", "Chiradzulu": "Malawi", "Nsanje": "Malawi", - "Abidjan": "Cote D'Ivoire", "Bouaké": "Cote D'Ivoire", "Daloa": "Cote D'Ivoire", "Yamoussoukro": "Cote D'Ivoire", - // Mozambique - "Maputo": "Mozambique", "Matola": "Mozambique", "Beira": "Mozambique", "Nampula": "Mozambique", - "Chimoio": "Mozambique", "Nacala": "Mozambique", "Quelimane": "Mozambique", "Tete": "Mozambique", - "Xai-Xai": "Mozambique", "Maxixe": "Mozambique", "Inhambane": "Mozambique", "Pemba": "Mozambique", - "Lomé": "Togo", "Sokodé": "Togo", "Kara": "Togo", "Palimé": "Togo", - // Tanzania - "Dar es Salaam": "Tanzania", "Mwanza": "Tanzania", "Arusha": "Tanzania", "Dodoma": "Tanzania", - "Mbeya": "Tanzania", "Morogoro": "Tanzania", "Tanga": "Tanzania", "Kahama": "Tanzania", - "Tabora": "Tanzania", "Zanzibar City": "Tanzania", "Kigoma": "Tanzania", "Sumbawanga": "Tanzania", - "Cotonou": "Benin", "Porto-Novo": "Benin", "Parakou": "Benin", "Djougou": "Benin", - // Rwanda - "Kigali": "Rwanda", "Butare": "Rwanda", "Gitarama": "Rwanda", "Ruhengeri": "Rwanda", - "Gisenyi": "Rwanda", "Byumba": "Rwanda", "Cyangugu": "Rwanda", "Kibuye": "Rwanda", - "Banjul": "Gambia", "Serekunda": "Gambia", "Brikama": "Gambia", "Bakau": "Gambia", - // Burundi - "Gitega": "Burundi", "Bujumbura": "Burundi", "Muyinga": "Burundi", "Ruyigi": "Burundi", - "Ngozi": "Burundi", "Rutana": "Burundi", "Kayanza": "Burundi", "Makamba": "Burundi", - "Nouakchott": "Mauritania", "Nouadhibou": "Mauritania", "Néma": "Mauritania", "Kaédi": "Mauritania", - // Uganda - "Kampala": "Uganda", "Gulu": "Uganda", "Lira": "Uganda", "Mbarara": "Uganda", - "Jinja": "Uganda", "Bwizibwera": "Uganda", "Mbale": "Uganda", "Mukono": "Uganda", - "Kasese": "Uganda", "Masaka": "Uganda", "Entebbe": "Uganda", "Njeru": "Uganda", - // Cabo Verde (补充缺失) - // Tunisia - "Tunis": "Tunisia", "Sfax": "Tunisia", "Sousse": "Tunisia", "Ettadhamen": "Tunisia", - "Kairouan": "Tunisia", "Bizerte": "Tunisia", "Gabès": "Tunisia", "Aryanah": "Tunisia", - "Gafsa": "Tunisia", "El Mourouj": "Tunisia", "Kasserine": "Tunisia", "Ben Arous": "Tunisia", - // São Tomé and Príncipe (补充缺失) - // Libya - "Benghazi": "Libya", "Misrata": "Libya", "Tarhuna": "Libya", - "Al Bayda": "Libya", "Zawiya": "Libya", "Zuwara": "Libya", "Ajdabiya": "Libya", - "Tobruk": "Libya", "Sabha": "Libya", "Sirte": "Libya", "Marj": "Libya", - "Dublin": "Ireland", "Cork": "Ireland", "Limerick": "Ireland", "Galway": "Ireland", - "Waterford": "Ireland", "Drogheda": "Ireland", "Dundalk": "Ireland", "Swords": "Ireland", - "Bray": "Ireland", "Navan": "Ireland", "Ennis": "Ireland", "Kilkenny": "Ireland", - - // More countries and cities can be added here... -} - -// GetCountryCenterByCountryOrCity 根据国家名称或城市名称获取国家中心经纬度 -// countryOrAbbr: 国家名称(全称或简称) -// city: 城市名称 -// 返回: 纬度, 经度, 是否找到 -func GetCountryCenterByCountryOrCity(countryOrAbbr, city string) (lat, lon string, found bool) { - // 1. 首先尝试国家简称转全称(大小写不敏感) - countryOrAbbr = strings.TrimSpace(countryOrAbbr) - if countryOrAbbr != "" { - // 尝试作为简称查找 - if fullName, ok := countryAbbr[strings.ToUpper(countryOrAbbr)]; ok { - countryOrAbbr = fullName - } - // 2. 直接查找国家中心点(大小写不敏感) - for country, center := range countryCenter { - if strings.EqualFold(country, countryOrAbbr) { - return fmt.Sprintf("%f", center[0]), fmt.Sprintf("%f", center[1]), true - } - } - } - - // 3. 通过城市查找国家(大小写不敏感) - city = strings.TrimSpace(city) - if city != "" { - for cityName, country := range cityToCountry { - if strings.EqualFold(cityName, city) { - if center, ok := countryCenter[country]; ok { - return fmt.Sprintf("%f", center[0]), fmt.Sprintf("%f", center[1]), true - } - } - } - } - - return "", "", false -} - -// GetCountryCenter 根据国家名称获取中心经纬度(兼容旧接口) -func GetCountryCenter(countryName string) (lat, lon string, found bool) { - return GetCountryCenterByCountryOrCity(countryName, "") -} - -// GetCountryCenterByCity 根据城市名称获取所在国家的中心经纬度 -func GetCountryCenterByCity(cityName string) (lat, lon string, found bool) { - return GetCountryCenterByCountryOrCity("", cityName) -} diff --git a/pkg/countryCenter/county_center_test.go b/pkg/countryCenter/county_center_test.go deleted file mode 100644 index ef1f8b1..0000000 --- a/pkg/countryCenter/county_center_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package countryCenter - -import ( - "testing" -) - -func TestGetCountryCenter(t *testing.T) { - lat, lon, found := GetCountryCenterByCountryOrCity("SG", "Singapore") - if !found { - t.Error("GetCountryCenter('HK') should return found = true") - } - t.Logf("lat = %v, lon = %v", lat, lon) - -} diff --git a/pkg/deduction/deduction.go b/pkg/deduction/deduction.go index 467b90b..46f32c8 100644 --- a/pkg/deduction/deduction.go +++ b/pkg/deduction/deduction.go @@ -1,95 +1,302 @@ +// Package deduction provides functionality for calculating remaining amounts +// in subscription billing systems, supporting various time units and traffic-based calculations. package deduction import ( - "log" + "errors" + "fmt" + "math" "time" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/pkg/tool" ) const ( - UnitTimeNoLimit = "NoLimit" - UnitTimeYear = "Year" - UnitTimeMonth = "Month" - UnitTimeDay = "Day" - UintTimeHour = "Hour" - UintTimeMinute = "Minute" + // Time unit constants for subscription billing + UnitTimeNoLimit = "NoLimit" // Unlimited time subscription + UnitTimeYear = "Year" // Annual subscription + UnitTimeMonth = "Month" // Monthly subscription + UnitTimeDay = "Day" // Daily subscription + UnitTimeHour = "Hour" // Hourly subscription + UnitTimeMinute = "Minute" // Per-minute subscription - ResetCycleNone = 0 - ResetCycle1st = 1 - ResetCycleMonthly = 2 - ResetCycleYear = 3 + // Reset cycle constants for traffic resets + ResetCycleNone = 0 // No reset cycle + ResetCycle1st = 1 // Reset on 1st of each month + ResetCycleMonthly = 2 // Reset monthly based on start date + ResetCycleYear = 3 // Reset yearly based on start date + + // Safety limits for overflow protection + maxInt64 = math.MaxInt64 + minInt64 = math.MinInt64 ) +// Error definitions for validation and calculation failures +var ( + ErrInvalidQuantity = errors.New("order quantity cannot be zero or negative") + ErrInvalidAmount = errors.New("order amount cannot be negative") + ErrInvalidTraffic = errors.New("traffic values cannot be negative") + ErrInvalidTimeRange = errors.New("expire time must be after start time") + ErrInvalidUnitTime = errors.New("invalid unit time") + ErrInvalidDeductionRatio = errors.New("deduction ratio must be between 0 and 100") + ErrOverflow = errors.New("calculation overflow") +) + +// Subscribe represents a subscription with time and traffic limits type Subscribe struct { - StartTime time.Time - ExpireTime time.Time - Traffic int64 - Download int64 - Upload int64 - UnitTime string - UnitPrice int64 - ResetCycle int64 - DeductionRatio int64 + StartTime time.Time // Subscription start time + ExpireTime time.Time // Subscription expiration time + Traffic int64 // Total traffic allowance in bytes + Download int64 // Downloaded traffic in bytes + Upload int64 // Uploaded traffic in bytes + UnitTime string // Time unit for billing (Year, Month, Day, etc.) + UnitPrice int64 // Price per unit time + ResetCycle int64 // Traffic reset cycle + DeductionRatio int64 // Deduction ratio for weighted calculations (0-100) } +// Order represents a purchase order for subscription calculation type Order struct { - Amount int64 - Quantity int64 + Amount int64 // Total order amount + Quantity int64 // Order quantity } -func CalculateRemainingAmount(sub Subscribe, order Order) int64 { - if sub.UnitTime == UnitTimeNoLimit && sub.ResetCycle != 0 { - return 0 +// Validate checks if the Subscribe struct contains valid data +func (s *Subscribe) Validate() error { + if s.Traffic < 0 || s.Download < 0 || s.Upload < 0 { + return ErrInvalidTraffic } - log.Printf("开始计算订单剩余价值") - // 实际单价 - sub.UnitPrice = order.Amount / order.Quantity - log.Printf("订阅实际单价: %d", sub.UnitPrice) - now := time.Now() + + if s.Download+s.Upload > s.Traffic { + return fmt.Errorf("download + upload (%d) cannot exceed total traffic (%d)", s.Download+s.Upload, s.Traffic) + } + + if !s.ExpireTime.After(s.StartTime) { + return ErrInvalidTimeRange + } + + if s.DeductionRatio < 0 || s.DeductionRatio > 100 { + return ErrInvalidDeductionRatio + } + + validUnitTimes := []string{UnitTimeNoLimit, UnitTimeYear, UnitTimeMonth, UnitTimeDay, UnitTimeHour, UnitTimeMinute} + valid := false + for _, ut := range validUnitTimes { + if s.UnitTime == ut { + valid = true + break + } + } + if !valid { + return ErrInvalidUnitTime + } + + return nil +} + +// Validate checks if the Order struct contains valid data +func (o *Order) Validate() error { + if o.Quantity <= 0 { + return ErrInvalidQuantity + } + if o.Amount < 0 { + return ErrInvalidAmount + } + return nil +} + +// safeMultiply performs multiplication with overflow protection +func safeMultiply(a, b int64) (int64, error) { + if a == 0 || b == 0 { + return 0, nil + } + + if a > 0 && b > 0 { + if a > maxInt64/b { + return 0, ErrOverflow + } + } else if a < 0 && b < 0 { + if a < maxInt64/b { + return 0, ErrOverflow + } + } else { + if (a > 0 && b < minInt64/a) || (a < 0 && b > minInt64/a) { + return 0, ErrOverflow + } + } + + return a * b, nil +} + +// safeAdd performs addition with overflow protection +func safeAdd(a, b int64) (int64, error) { + if (b > 0 && a > maxInt64-b) || (b < 0 && a < minInt64-b) { + return 0, ErrOverflow + } + return a + b, nil +} + +// safeDivide performs division with zero-division protection +func safeDivide(a, b int64) (int64, error) { + if b == 0 { + return 0, errors.New("division by zero") + } + return a / b, nil +} + +// CalculateRemainingAmount calculates the remaining refund amount for a subscription +// based on unused time and traffic. Returns the amount and any calculation errors. +func CalculateRemainingAmount(sub Subscribe, order Order) (int64, error) { + if err := sub.Validate(); err != nil { + return 0, fmt.Errorf("invalid subscription: %w", err) + } + + if err := order.Validate(); err != nil { + return 0, fmt.Errorf("invalid order: %w", err) + } + + if sub.UnitTime == UnitTimeNoLimit && sub.ResetCycle != 0 { + return 0, nil + } + + unitPrice, err := safeDivide(order.Amount, order.Quantity) + if err != nil { + return 0, fmt.Errorf("failed to calculate unit price: %w", err) + } + sub.UnitPrice = unitPrice + + loc, err := time.LoadLocation(sub.StartTime.Location().String()) + if err != nil { + loc = time.UTC + } + now := time.Now().In(loc) + switch sub.UnitTime { case UnitTimeNoLimit: - log.Printf("订阅不限时长") - usedTraffic := sub.Traffic - sub.Download - sub.Upload - unitPrice := float64(order.Amount) / float64(sub.Traffic) - return int64(float64(usedTraffic) * unitPrice) + return calculateNoLimitAmount(sub, order) case UnitTimeYear: - log.Printf("订阅时长为年") remainingYears := tool.YearDiff(now, sub.ExpireTime) - remainingUnitTimeAmount := calculateRemainingUnitTimeAmount(sub) - return int64(remainingYears)*sub.UnitPrice + remainingUnitTimeAmount + remainingUnitTimeAmount, err := calculateRemainingUnitTimeAmount(sub) + if err != nil { + return 0, err + } + + yearAmount, err := safeMultiply(int64(remainingYears), sub.UnitPrice) + if err != nil { + return 0, fmt.Errorf("year calculation overflow: %w", err) + } + + total, err := safeAdd(yearAmount, remainingUnitTimeAmount) + if err != nil { + return 0, fmt.Errorf("total calculation overflow: %w", err) + } + + return total, nil case UnitTimeMonth: - log.Printf("订阅时长为月") remainingMonths := tool.MonthDiff(now, sub.ExpireTime) - remainingUnitTimeAmount := calculateRemainingUnitTimeAmount(sub) - return int64(remainingMonths)*sub.UnitPrice + remainingUnitTimeAmount + remainingUnitTimeAmount, err := calculateRemainingUnitTimeAmount(sub) + if err != nil { + return 0, err + } + + monthAmount, err := safeMultiply(int64(remainingMonths), sub.UnitPrice) + if err != nil { + return 0, fmt.Errorf("month calculation overflow: %w", err) + } + + total, err := safeAdd(monthAmount, remainingUnitTimeAmount) + if err != nil { + return 0, fmt.Errorf("total calculation overflow: %w", err) + } + + return total, nil + + case UnitTimeDay: + remainingDays := tool.DayDiff(now, sub.ExpireTime) + remainingUnitTimeAmount, err := calculateRemainingUnitTimeAmount(sub) + if err != nil { + return 0, err + } + + dayAmount, err := safeMultiply(remainingDays, sub.UnitPrice) + if err != nil { + return 0, fmt.Errorf("day calculation overflow: %w", err) + } + + total, err := safeAdd(dayAmount, remainingUnitTimeAmount) + if err != nil { + return 0, fmt.Errorf("total calculation overflow: %w", err) + } + + return total, nil } - return 0 + return 0, nil } -func calculateRemainingUnitTimeAmount(sub Subscribe) int64 { +// calculateNoLimitAmount calculates refund amount for unlimited time subscriptions +// based on unused traffic only +func calculateNoLimitAmount(sub Subscribe, order Order) (int64, error) { + if sub.Traffic == 0 { + return 0, nil + } + + usedTraffic := sub.Traffic - sub.Download - sub.Upload + if usedTraffic < 0 { + usedTraffic = 0 + } + + unitPrice := float64(order.Amount) / float64(sub.Traffic) + result := float64(usedTraffic) * unitPrice + + if result > float64(maxInt64) || result < float64(minInt64) { + return 0, ErrOverflow + } + + return int64(result), nil +} + +// calculateRemainingUnitTimeAmount calculates the remaining amount based on +// both time and traffic usage, applying deduction ratios when specified +func calculateRemainingUnitTimeAmount(sub Subscribe) (int64, error) { now := time.Now() - log.Printf("开始计算订阅剩余时长价值") - log.Printf("订阅开始时间: %s, 订阅到期时间: %s,订阅流量: %d", sub.StartTime.Format("2006-01-02 15:04:05"), sub.ExpireTime.Format("2006-01-02 15:04:05"), sub.Traffic) trafficWeight, timeWeight := calculateWeights(sub.DeductionRatio) remainingDays, totalDays := getRemainingAndTotalDays(sub, now) - remainingTraffic := sub.Traffic - sub.Download - sub.Upload - remainingTimeAmount := calculateProportionalAmount(sub.UnitPrice, remainingDays, totalDays) - remainingTrafficAmount := calculateProportionalAmount(sub.UnitPrice, remainingTraffic, sub.Traffic) - log.Printf("订阅剩余天数: %d, 总天数: %d, 剩余流量: %d, 剩余时间价值: %d, 剩余流量价值: %d", remainingDays, totalDays, remainingTraffic, remainingTimeAmount, remainingTrafficAmount) - if sub.Traffic == 0 { - return remainingTimeAmount + + if totalDays == 0 { + return 0, nil } + + remainingTraffic := sub.Traffic - sub.Download - sub.Upload + if remainingTraffic < 0 { + remainingTraffic = 0 + } + + remainingTimeAmount, err := calculateProportionalAmount(sub.UnitPrice, remainingDays, totalDays) + if err != nil { + return 0, fmt.Errorf("time amount calculation failed: %w", err) + } + + if sub.Traffic == 0 { + return remainingTimeAmount, nil + } + + remainingTrafficAmount, err := calculateProportionalAmount(sub.UnitPrice, remainingTraffic, sub.Traffic) + if err != nil { + return 0, fmt.Errorf("traffic amount calculation failed: %w", err) + } + if sub.DeductionRatio != 0 { return calculateWeightedAmount(sub.UnitPrice, remainingTraffic, sub.Traffic, remainingDays, totalDays, trafficWeight, timeWeight) } - return min(remainingTimeAmount, remainingTrafficAmount) + return min(remainingTimeAmount, remainingTrafficAmount), nil } +// calculateWeights converts deduction ratio to traffic and time weights +// for weighted calculations func calculateWeights(deductionRatio int64) (float64, float64) { if deductionRatio == 0 { return 0, 0 @@ -99,22 +306,32 @@ func calculateWeights(deductionRatio int64) (float64, float64) { return trafficWeight, timeWeight } +// getRemainingAndTotalDays calculates remaining and total days based on +// the subscription's reset cycle configuration func getRemainingAndTotalDays(sub Subscribe, now time.Time) (int64, int64) { - log.Printf("开始计算订阅剩余天数") - log.Printf("重置周期: %d", sub.ResetCycle) switch sub.ResetCycle { case ResetCycleNone: - remaining := sub.ExpireTime.Sub(now).Hours() / 24 total := sub.ExpireTime.Sub(sub.StartTime).Hours() / 24 + if remaining < 0 { + remaining = 0 + } + if total < 0 { + total = 0 + } return int64(remaining), int64(total) case ResetCycle1st: return tool.DaysToNextMonth(now), tool.GetLastDayOfMonth(now) case ResetCycleMonthly: - // -1 to include the current day - return tool.DaysToMonthDay(now, sub.StartTime.Day()) - 1, tool.DaysToMonthDay(now, sub.StartTime.Day()) + remaining := tool.DaysToMonthDay(now, sub.StartTime.Day()) - 1 + total := tool.DaysToMonthDay(now, sub.StartTime.Day()) + if remaining < 0 { + remaining = 0 + } + return remaining, total + case ResetCycleYear: return tool.DaysToYearDay(now, int(sub.StartTime.Month()), sub.StartTime.Day()), tool.GetYearDays(now, int(sub.StartTime.Month()), sub.StartTime.Day()) @@ -122,13 +339,36 @@ func getRemainingAndTotalDays(sub Subscribe, now time.Time) (int64, int64) { return 0, 0 } -func calculateWeightedAmount(unitPrice, remainingTraffic, totalTraffic, remainingDays, totalDays int64, trafficWeight, timeWeight float64) int64 { +// calculateWeightedAmount applies weighted calculation combining both time and traffic +// remaining ratios based on the specified weights +func calculateWeightedAmount(unitPrice, remainingTraffic, totalTraffic, remainingDays, totalDays int64, trafficWeight, timeWeight float64) (int64, error) { + if totalDays == 0 || totalTraffic == 0 { + return 0, nil + } + remainingTimeRatio := float64(remainingDays) / float64(totalDays) remainingTrafficRatio := float64(remainingTraffic) / float64(totalTraffic) weightedRemainingRatio := (timeWeight * remainingTimeRatio) + (trafficWeight * remainingTrafficRatio) - return int64(float64(unitPrice) * weightedRemainingRatio) + + result := float64(unitPrice) * weightedRemainingRatio + if result > float64(maxInt64) || result < float64(minInt64) { + return 0, ErrOverflow + } + + return int64(result), nil } -func calculateProportionalAmount(unitPrice, remaining, total int64) int64 { - return int64(float64(unitPrice) * (float64(remaining) / float64(total))) +// calculateProportionalAmount calculates proportional amount based on +// remaining vs total ratio with overflow protection +func calculateProportionalAmount(unitPrice, remaining, total int64) (int64, error) { + if total == 0 { + return 0, nil + } + + result := float64(unitPrice) * (float64(remaining) / float64(total)) + if result > float64(maxInt64) || result < float64(minInt64) { + return 0, ErrOverflow + } + + return int64(result), nil } diff --git a/pkg/deduction/deduction_test.go b/pkg/deduction/deduction_test.go new file mode 100644 index 0000000..0e96555 --- /dev/null +++ b/pkg/deduction/deduction_test.go @@ -0,0 +1,665 @@ +package deduction + +import ( + "math" + "testing" + "time" +) + +func TestSubscribe_Validate(t *testing.T) { + tests := []struct { + name string + sub Subscribe + wantErr bool + errType error + }{ + { + name: "valid subscription", + sub: Subscribe{ + StartTime: time.Now(), + ExpireTime: time.Now().Add(24 * time.Hour), + Traffic: 1000, + Download: 100, + Upload: 200, + UnitTime: UnitTimeMonth, + DeductionRatio: 50, + }, + wantErr: false, + }, + { + name: "negative traffic", + sub: Subscribe{ + StartTime: time.Now(), + ExpireTime: time.Now().Add(24 * time.Hour), + Traffic: -1000, + Download: 100, + Upload: 200, + UnitTime: UnitTimeMonth, + DeductionRatio: 50, + }, + wantErr: true, + errType: ErrInvalidTraffic, + }, + { + name: "negative download", + sub: Subscribe{ + StartTime: time.Now(), + ExpireTime: time.Now().Add(24 * time.Hour), + Traffic: 1000, + Download: -100, + Upload: 200, + UnitTime: UnitTimeMonth, + DeductionRatio: 50, + }, + wantErr: true, + errType: ErrInvalidTraffic, + }, + { + name: "download + upload exceeds traffic", + sub: Subscribe{ + StartTime: time.Now(), + ExpireTime: time.Now().Add(24 * time.Hour), + Traffic: 1000, + Download: 600, + Upload: 500, + UnitTime: UnitTimeMonth, + DeductionRatio: 50, + }, + wantErr: true, + }, + { + name: "expire time before start time", + sub: Subscribe{ + StartTime: time.Now(), + ExpireTime: time.Now().Add(-24 * time.Hour), + Traffic: 1000, + Download: 100, + Upload: 200, + UnitTime: UnitTimeMonth, + DeductionRatio: 50, + }, + wantErr: true, + errType: ErrInvalidTimeRange, + }, + { + name: "invalid deduction ratio - negative", + sub: Subscribe{ + StartTime: time.Now(), + ExpireTime: time.Now().Add(24 * time.Hour), + Traffic: 1000, + Download: 100, + Upload: 200, + UnitTime: UnitTimeMonth, + DeductionRatio: -10, + }, + wantErr: true, + errType: ErrInvalidDeductionRatio, + }, + { + name: "invalid deduction ratio - over 100", + sub: Subscribe{ + StartTime: time.Now(), + ExpireTime: time.Now().Add(24 * time.Hour), + Traffic: 1000, + Download: 100, + Upload: 200, + UnitTime: UnitTimeMonth, + DeductionRatio: 150, + }, + wantErr: true, + errType: ErrInvalidDeductionRatio, + }, + { + name: "invalid unit time", + sub: Subscribe{ + StartTime: time.Now(), + ExpireTime: time.Now().Add(24 * time.Hour), + Traffic: 1000, + Download: 100, + Upload: 200, + UnitTime: "InvalidUnit", + DeductionRatio: 50, + }, + wantErr: true, + errType: ErrInvalidUnitTime, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.sub.Validate() + if (err != nil) != tt.wantErr { + t.Errorf("Subscribe.Validate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.errType != nil && err != tt.errType { + t.Errorf("Subscribe.Validate() error = %v, want %v", err, tt.errType) + } + }) + } +} + +func TestOrder_Validate(t *testing.T) { + tests := []struct { + name string + order Order + wantErr bool + errType error + }{ + { + name: "valid order", + order: Order{Amount: 1000, Quantity: 2}, + wantErr: false, + }, + { + name: "zero quantity", + order: Order{Amount: 1000, Quantity: 0}, + wantErr: true, + errType: ErrInvalidQuantity, + }, + { + name: "negative quantity", + order: Order{Amount: 1000, Quantity: -1}, + wantErr: true, + errType: ErrInvalidQuantity, + }, + { + name: "negative amount", + order: Order{Amount: -1000, Quantity: 2}, + wantErr: true, + errType: ErrInvalidAmount, + }, + { + name: "zero amount is valid", + order: Order{Amount: 0, Quantity: 1}, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.order.Validate() + if (err != nil) != tt.wantErr { + t.Errorf("Order.Validate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.errType != nil && err != tt.errType { + t.Errorf("Order.Validate() error = %v, want %v", err, tt.errType) + } + }) + } +} + +func TestSafeMultiply(t *testing.T) { + tests := []struct { + name string + a, b int64 + want int64 + wantErr bool + }{ + { + name: "normal multiplication", + a: 10, + b: 20, + want: 200, + wantErr: false, + }, + { + name: "zero multiplication", + a: 10, + b: 0, + want: 0, + wantErr: false, + }, + { + name: "negative multiplication", + a: -10, + b: 20, + want: -200, + wantErr: false, + }, + { + name: "overflow case", + a: math.MaxInt64, + b: 2, + want: 0, + wantErr: true, + }, + { + name: "large numbers no overflow", + a: 1000000, + b: 1000000, + want: 1000000000000, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := safeMultiply(tt.a, tt.b) + if (err != nil) != tt.wantErr { + t.Errorf("safeMultiply() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("safeMultiply() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSafeAdd(t *testing.T) { + tests := []struct { + name string + a, b int64 + want int64 + wantErr bool + }{ + { + name: "normal addition", + a: 10, + b: 20, + want: 30, + wantErr: false, + }, + { + name: "negative addition", + a: -10, + b: 5, + want: -5, + wantErr: false, + }, + { + name: "overflow case", + a: math.MaxInt64, + b: 1, + want: 0, + wantErr: true, + }, + { + name: "underflow case", + a: math.MinInt64, + b: -1, + want: 0, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := safeAdd(tt.a, tt.b) + if (err != nil) != tt.wantErr { + t.Errorf("safeAdd() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("safeAdd() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSafeDivide(t *testing.T) { + tests := []struct { + name string + a, b int64 + want int64 + wantErr bool + }{ + { + name: "normal division", + a: 20, + b: 10, + want: 2, + wantErr: false, + }, + { + name: "division by zero", + a: 20, + b: 0, + want: 0, + wantErr: true, + }, + { + name: "negative division", + a: -20, + b: 10, + want: -2, + wantErr: false, + }, + { + name: "zero dividend", + a: 0, + b: 10, + want: 0, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := safeDivide(tt.a, tt.b) + if (err != nil) != tt.wantErr { + t.Errorf("safeDivide() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("safeDivide() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCalculateWeights(t *testing.T) { + tests := []struct { + name string + deductionRatio int64 + wantTrafficWeight float64 + wantTimeWeight float64 + }{ + { + name: "zero ratio", + deductionRatio: 0, + wantTrafficWeight: 0, + wantTimeWeight: 0, + }, + { + name: "50% ratio", + deductionRatio: 50, + wantTrafficWeight: 0.5, + wantTimeWeight: 0.5, + }, + { + name: "75% ratio", + deductionRatio: 75, + wantTrafficWeight: 0.75, + wantTimeWeight: 0.25, + }, + { + name: "100% ratio", + deductionRatio: 100, + wantTrafficWeight: 1.0, + wantTimeWeight: 0.0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotTrafficWeight, gotTimeWeight := calculateWeights(tt.deductionRatio) + if gotTrafficWeight != tt.wantTrafficWeight { + t.Errorf("calculateWeights() trafficWeight = %v, want %v", gotTrafficWeight, tt.wantTrafficWeight) + } + if gotTimeWeight != tt.wantTimeWeight { + t.Errorf("calculateWeights() timeWeight = %v, want %v", gotTimeWeight, tt.wantTimeWeight) + } + }) + } +} + +func TestCalculateProportionalAmount(t *testing.T) { + tests := []struct { + name string + unitPrice int64 + remaining int64 + total int64 + want int64 + wantErr bool + }{ + { + name: "normal calculation", + unitPrice: 100, + remaining: 50, + total: 100, + want: 50, + wantErr: false, + }, + { + name: "zero total", + unitPrice: 100, + remaining: 50, + total: 0, + want: 0, + wantErr: false, + }, + { + name: "zero remaining", + unitPrice: 100, + remaining: 0, + total: 100, + want: 0, + wantErr: false, + }, + { + name: "quarter remaining", + unitPrice: 200, + remaining: 25, + total: 100, + want: 50, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := calculateProportionalAmount(tt.unitPrice, tt.remaining, tt.total) + if (err != nil) != tt.wantErr { + t.Errorf("calculateProportionalAmount() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("calculateProportionalAmount() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCalculateNoLimitAmount(t *testing.T) { + tests := []struct { + name string + sub Subscribe + order Order + want int64 + wantErr bool + }{ + { + name: "normal no limit calculation", + sub: Subscribe{ + Traffic: 1000, + Download: 300, + Upload: 200, + }, + order: Order{ + Amount: 1000, + }, + want: 500, // (1000 - 300 - 200) / 1000 * 1000 = 500 + wantErr: false, + }, + { + name: "zero traffic", + sub: Subscribe{ + Traffic: 0, + Download: 0, + Upload: 0, + }, + order: Order{ + Amount: 1000, + }, + want: 0, + wantErr: false, + }, + { + name: "overused traffic", + sub: Subscribe{ + Traffic: 1000, + Download: 600, + Upload: 500, + }, + order: Order{ + Amount: 1000, + }, + want: 0, // usedTraffic would be negative, clamped to 0 + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := calculateNoLimitAmount(tt.sub, tt.order) + if (err != nil) != tt.wantErr { + t.Errorf("calculateNoLimitAmount() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("calculateNoLimitAmount() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCalculateRemainingAmount(t *testing.T) { + now := time.Now() + + tests := []struct { + name string + sub Subscribe + order Order + wantErr bool + }{ + { + name: "valid no limit subscription", + sub: Subscribe{ + StartTime: now.Add(-24 * time.Hour), + ExpireTime: now.Add(24 * time.Hour), + Traffic: 1000, + Download: 300, + Upload: 200, + UnitTime: UnitTimeNoLimit, + ResetCycle: ResetCycleNone, + DeductionRatio: 0, + }, + order: Order{ + Amount: 1000, + Quantity: 1, + }, + wantErr: false, + }, + { + name: "invalid subscription", + sub: Subscribe{ + StartTime: now, + ExpireTime: now.Add(-24 * time.Hour), // Invalid: expire before start + Traffic: 1000, + Download: 300, + Upload: 200, + UnitTime: UnitTimeMonth, + DeductionRatio: 0, + }, + order: Order{ + Amount: 1000, + Quantity: 1, + }, + wantErr: true, + }, + { + name: "invalid order", + sub: Subscribe{ + StartTime: now.Add(-24 * time.Hour), + ExpireTime: now.Add(24 * time.Hour), + Traffic: 1000, + Download: 300, + Upload: 200, + UnitTime: UnitTimeMonth, + DeductionRatio: 0, + }, + order: Order{ + Amount: 1000, + Quantity: 0, // Invalid: zero quantity + }, + wantErr: true, + }, + { + name: "no limit with reset cycle", + sub: Subscribe{ + StartTime: now.Add(-24 * time.Hour), + ExpireTime: now.Add(24 * time.Hour), + Traffic: 1000, + Download: 300, + Upload: 200, + UnitTime: UnitTimeNoLimit, + ResetCycle: ResetCycleMonthly, // Should return 0 + DeductionRatio: 0, + }, + order: Order{ + Amount: 1000, + Quantity: 1, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := CalculateRemainingAmount(tt.sub, tt.order) + if (err != nil) != tt.wantErr { + t.Errorf("CalculateRemainingAmount() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCalculateRemainingAmount_NoLimitWithResetCycle(t *testing.T) { + now := time.Now() + sub := Subscribe{ + StartTime: now.Add(-24 * time.Hour), + ExpireTime: now.Add(24 * time.Hour), + Traffic: 1000, + Download: 300, + Upload: 200, + UnitTime: UnitTimeNoLimit, + ResetCycle: ResetCycleMonthly, + DeductionRatio: 0, + } + order := Order{ + Amount: 1000, + Quantity: 1, + } + + got, err := CalculateRemainingAmount(sub, order) + if err != nil { + t.Errorf("CalculateRemainingAmount() error = %v", err) + return + } + if got != 0 { + t.Errorf("CalculateRemainingAmount() = %v, want 0", got) + } +} + +// Benchmark tests +func BenchmarkCalculateRemainingAmount(b *testing.B) { + now := time.Now() + sub := Subscribe{ + StartTime: now.Add(-24 * time.Hour), + ExpireTime: now.Add(24 * time.Hour), + Traffic: 1000, + Download: 300, + Upload: 200, + UnitTime: UnitTimeMonth, + ResetCycle: ResetCycleNone, + DeductionRatio: 50, + } + order := Order{ + Amount: 1000, + Quantity: 1, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = CalculateRemainingAmount(sub, order) + } +} + +func BenchmarkSafeMultiply(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = safeMultiply(12345, 67890) + } +} diff --git a/pkg/email/manager.go b/pkg/email/manager.go new file mode 100644 index 0000000..8e4b8a3 --- /dev/null +++ b/pkg/email/manager.go @@ -0,0 +1,134 @@ +package email + +import ( + "context" + "sync" + "time" + + "github.com/perfect-panel/server/pkg/logger" + "gorm.io/gorm" +) + +var ( + Manager *WorkerManager // 全局调度器实例 + once sync.Once // 确保 Scheduler 只被初始化一次 + limit sync.RWMutex // 控制并发限制 +) + +type WorkerManager struct { + db *gorm.DB // 数据库连接 + sender Sender // 邮件发送器接口 + mutex sync.RWMutex // 读写互斥锁,确保线程安全 + workers map[int64]*Worker // 存储所有 Worker 实例 + cancels map[int64]context.CancelFunc // 存储每个 Worker 的取消函数 +} + +func NewWorkerManager(db *gorm.DB, sender Sender) *WorkerManager { + if Manager != nil { + return Manager + } + once.Do(func() { + Manager = &WorkerManager{ + db: db, + workers: make(map[int64]*Worker), + cancels: make(map[int64]context.CancelFunc), + sender: sender, + } + }) + // 设置定时检查任务 + go func() { + for { + // 每隔5分钟检查一次 + select { + case <-time.After(1 * time.Minute): + checkWorker() + continue + } + } + }() + return Manager +} + +// AddWorker 添加一个新的 Worker 实例 +func (m *WorkerManager) AddWorker(id int64) { + m.mutex.Lock() + defer m.mutex.Unlock() + if _, exists := m.workers[id]; !exists { + ctx, cancel := context.WithCancel(context.Background()) + worker := NewWorker(ctx, id, m.db, m.sender) + m.workers[id] = worker + m.cancels[id] = cancel + go worker.Start() + logger.Info("Batch Send Email", + logger.Field("message", "Added new worker"), + logger.Field("task_id", id), + ) + } else { + logger.Info("Batch Send Email", + logger.Field("message", "Worker already exists"), + logger.Field("task_id", id), + ) + } + +} + +// GetWorker 获取指定任务的 Worker 实例 +func (m *WorkerManager) GetWorker(id int64) *Worker { + m.mutex.RLock() + defer m.mutex.RUnlock() + if worker, exists := m.workers[id]; exists { + return worker + } else { + logger.Error("Batch Send Email", + logger.Field("message", "Worker not found"), + logger.Field("task_id", id), + ) + return nil + } +} + +// RemoveWorker 移除指定任务的 Worker 实例 +func (m *WorkerManager) RemoveWorker(id int64) { + m.mutex.Lock() + defer m.mutex.Unlock() + if _, exists := m.workers[id]; exists { + delete(m.workers, id) + if cancelFunc, ok := m.cancels[id]; ok { + cancelFunc() // 调用取消函数 + delete(m.cancels, id) + } + logger.Info("Batch Send Email", + logger.Field("message", "Removed worker"), + logger.Field("task_id", id), + ) + } else { + logger.Error("Batch Send Email", + logger.Field("message", "Worker not found for removal"), + logger.Field("task_id", id), + ) + } +} + +func checkWorker() { + if Manager == nil { + // 如果 Manager 未初始化,直接返回 + return + } + Manager.mutex.Lock() + defer Manager.mutex.Unlock() + for id, worker := range Manager.workers { + if worker.IsRunning() == 2 { + // 如果Worker已完成,移除它 + delete(Manager.workers, id) + if cancelFunc, ok := Manager.cancels[id]; ok { + cancelFunc() // 调用取消函数 + delete(Manager.cancels, id) + } + logger.Info("Batch Send Email", + logger.Field("message", "Removed completed worker"), + logger.Field("task_id", id), + ) + } + } + +} diff --git a/pkg/email/platform.go b/pkg/email/platform.go index 95d737c..515ef45 100644 --- a/pkg/email/platform.go +++ b/pkg/email/platform.go @@ -1,6 +1,6 @@ package email -import "github.com/perfect-panel/ppanel-server/internal/types" +import "github.com/perfect-panel/server/internal/types" type Platform int diff --git a/pkg/email/sender.go b/pkg/email/sender.go index d1e6570..7bbe1fc 100644 --- a/pkg/email/sender.go +++ b/pkg/email/sender.go @@ -4,8 +4,8 @@ import ( "encoding/json" "fmt" - "github.com/perfect-panel/ppanel-server/pkg/email/smtp" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/email/smtp" + "github.com/perfect-panel/server/pkg/logger" ) type Sender interface { diff --git a/pkg/email/template.go b/pkg/email/template.go index f31b9c7..5d2bd5b 100644 --- a/pkg/email/template.go +++ b/pkg/email/template.go @@ -282,7 +282,6 @@ const ( ` - DefaultTrafficExceedEmailTemplate = ` diff --git a/pkg/email/worker.go b/pkg/email/worker.go new file mode 100644 index 0000000..fd5aba4 --- /dev/null +++ b/pkg/email/worker.go @@ -0,0 +1,192 @@ +package email + +import ( + "context" + "encoding/json" + "time" + + "github.com/perfect-panel/server/internal/model/task" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/tool" + "gorm.io/gorm" +) + +type ErrorInfo struct { + Error string `json:"error"` + Email string `json:"email"` + Time int64 `json:"time"` +} + +type Worker struct { + id int64 // 任务ID + db *gorm.DB // 数据库连接 + ctx context.Context // 上下文 + sender Sender // 邮件发送器接口 + status uint8 // 任务状态,0 表示未运行,1 表示运行中 2 表示已完成 +} + +func NewWorker(ctx context.Context, id int64, db *gorm.DB, sender Sender) *Worker { + return &Worker{ + id: id, + db: db, + ctx: ctx, + sender: sender, + } +} + +// GetID 获取Worker的任务ID +func (w *Worker) GetID() int64 { + return w.id +} + +// IsRunning 检查Worker是否正在运行 +func (w *Worker) IsRunning() uint8 { + return w.status +} + +// Start 启动Worker,开始处理任务 +func (w *Worker) Start() { + // 检查并发限制 + limit.Lock() + defer limit.Unlock() + tx := w.db.WithContext(w.ctx) + var taskInfo task.Task + if err := tx.Model(&task.Task{}).Where("id = ?", w.id).First(&taskInfo).Error; err != nil { + logger.Error("Batch Send Email", + logger.Field("message", "Failed to find task"), + logger.Field("error", err.Error()), + logger.Field("task_id", w.id), + ) + return + } + if taskInfo.Status != 0 { + logger.Error("Batch Send Email", + logger.Field("message", "Task already completed or in progress"), + logger.Field("task_id", w.id), + ) + return + } + + var scope task.EmailScope + if err := json.Unmarshal([]byte(taskInfo.Scope), &scope); err != nil { + logger.Error("Batch Send Email", + logger.Field("message", "Failed to parse task scope"), + logger.Field("error", err.Error()), + logger.Field("task_id", w.id), + ) + return + } + + if len(scope.Recipients) == 0 && len(scope.Additional) == 0 { + logger.Error("Batch Send Email", + logger.Field("message", "No recipients or additional emails provided"), + logger.Field("task_id", w.id), + ) + return + } + + var content task.EmailContent + if err := json.Unmarshal([]byte(taskInfo.Content), &content); err != nil { + logger.Error("Batch Send Email", + logger.Field("message", "Failed to parse task content"), + logger.Field("error", err.Error()), + logger.Field("task_id", w.id), + ) + return + } + + w.status = 1 // 设置状态为运行中 + var recipients []string + // 解析收件人 + if len(scope.Recipients) > 0 { + recipients = append(recipients, scope.Recipients...) + } + // 解析附加收件人 + if len(scope.Additional) > 0 { + recipients = append(recipients, scope.Additional...) + } + // 去重和清理空字符串 + recipients = tool.RemoveDuplicateElements(recipients...) + + if len(recipients) == 0 { + logger.Error("Batch Send Email", + logger.Field("message", "No valid recipients found"), + logger.Field("task_id", w.id), + ) + w.status = 2 // 设置状态为已完成 + return + } + + // 设置发送间隔时间 + var intervalTime time.Duration + if scope.Interval == 0 { + intervalTime = 1 * time.Second + } else { + intervalTime = time.Duration(scope.Interval) * time.Second + } + + var errors []ErrorInfo + var count uint64 + for _, recipient := range recipients { + select { + case <-w.ctx.Done(): + logger.Info("Batch Send Email", + logger.Field("message", "Worker stopped by context cancellation"), + logger.Field("task_id", w.id), + ) + return + default: + } + if taskInfo.Status == 0 { + taskInfo.Status = 1 // 1 表示任务进行中 + } + + if err := w.sender.Send([]string{recipient}, content.Subject, content.Content); err != nil { + logger.Error("Batch Send Email", + logger.Field("message", "Failed to send email"), + logger.Field("error", err.Error()), + logger.Field("recipient", recipient), + logger.Field("task_id", w.id), + ) + errors = append(errors, ErrorInfo{ + Error: err.Error(), + Email: recipient, + Time: time.Now().Unix(), + }) + text, _ := json.Marshal(errors) + taskInfo.Errors = string(text) + } + count++ + taskInfo.Current = count + if err := tx.Model(&task.Task{}).Where("`id` = ?", taskInfo.Id).Save(&taskInfo).Error; err != nil { + logger.Error("Batch Send Email", + logger.Field("message", "Failed to update task progress"), + logger.Field("error", err.Error()), + logger.Field("task_id", w.id), + ) + errors = append(errors, ErrorInfo{ + Error: err.Error(), + Email: recipient, + Time: time.Now().Unix(), + }) + w.status = 2 // 设置状态为已完成 + } + time.Sleep(intervalTime) + } + taskInfo.Status = 2 // 2 表示任务已完成 + w.status = 2 // 设置状态为已完成 + + if err := tx.Model(&task.Task{}).Where("`id` = ?", taskInfo.Id).Save(&taskInfo).Error; err != nil { + logger.Error("Batch Send Email", + logger.Field("message", "Failed to finalize task"), + logger.Field("error", err.Error()), + logger.Field("task_id", w.id), + ) + } else { + logger.Info("Batch Send Email", + logger.Field("message", "Task completed successfully"), + logger.Field("task_id", w.id), + logger.Field("total_sent", count), + ) + } +} diff --git a/pkg/fs/temps.go b/pkg/fs/temps.go index f1ead01..a7ad7b3 100644 --- a/pkg/fs/temps.go +++ b/pkg/fs/temps.go @@ -3,7 +3,7 @@ package fs import ( "os" - "github.com/perfect-panel/ppanel-server/pkg/hash" + "github.com/perfect-panel/server/pkg/hash" ) // TempFileWithText creates the temporary file with the given content, diff --git a/pkg/hash/consistenthash.go b/pkg/hash/consistenthash.go index 0e67126..6aae91d 100644 --- a/pkg/hash/consistenthash.go +++ b/pkg/hash/consistenthash.go @@ -6,7 +6,7 @@ import ( "strconv" "sync" - "github.com/perfect-panel/ppanel-server/pkg/lang" + "github.com/perfect-panel/server/pkg/lang" ) const ( diff --git a/pkg/limit/tokenlimit.go b/pkg/limit/tokenlimit.go index ad42241..19d9ed0 100644 --- a/pkg/limit/tokenlimit.go +++ b/pkg/limit/tokenlimit.go @@ -10,8 +10,8 @@ import ( "sync/atomic" "time" - "github.com/perfect-panel/ppanel-server/pkg/errorx" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/errorx" + "github.com/perfect-panel/server/pkg/logger" "github.com/redis/go-redis/v9" xrate "golang.org/x/time/rate" ) diff --git a/pkg/logger/color.go b/pkg/logger/color.go index fa33495..c56977d 100644 --- a/pkg/logger/color.go +++ b/pkg/logger/color.go @@ -3,7 +3,7 @@ package logger import ( "sync/atomic" - "github.com/perfect-panel/ppanel-server/pkg/color" + "github.com/perfect-panel/server/pkg/color" ) // WithColor is a helper function to add color to a string, only in plain encoding. diff --git a/pkg/logger/color_test.go b/pkg/logger/color_test.go index b6de740..b2bb45d 100644 --- a/pkg/logger/color_test.go +++ b/pkg/logger/color_test.go @@ -4,7 +4,7 @@ import ( "sync/atomic" "testing" - "github.com/perfect-panel/ppanel-server/pkg/color" + "github.com/perfect-panel/server/pkg/color" "github.com/stretchr/testify/assert" ) diff --git a/pkg/logger/config.go b/pkg/logger/config.go index 8f75b02..dceb82d 100644 --- a/pkg/logger/config.go +++ b/pkg/logger/config.go @@ -8,13 +8,13 @@ type LogConf struct { // console: log to console. // file: log to file. // volume: used in k8s, prepend the hostname to the log file name. - Mode string `yaml:"Mode" default:"console"` + Mode string `yaml:"Mode" default:"file"` // Encoding represents the encoding type, default is `json`. // json: json encoding. // plain: plain text encoding, typically used in development. Encoding string `yaml:"Encoding" default:"json"` // TimeFormat represents the time format, default is `2006-01-02T15:04:05.000Z07:00`. - TimeFormat string `yaml:"TimeFormat" default:"2006-01-02T15:04:05.000Z07:00"` + TimeFormat string `yaml:"TimeFormat" default:"2006-01-02 15:04:05.000"` // Path represents the log file path, default is `logs`. Path string `yaml:"Path" default:"logs"` // Level represents the log level, default is `info`. diff --git a/pkg/logger/limitedexecutor.go b/pkg/logger/limitedexecutor.go index 6db3271..cec494c 100644 --- a/pkg/logger/limitedexecutor.go +++ b/pkg/logger/limitedexecutor.go @@ -4,8 +4,8 @@ import ( "sync/atomic" "time" - "github.com/perfect-panel/ppanel-server/pkg/syncx" - "github.com/perfect-panel/ppanel-server/pkg/timex" + "github.com/perfect-panel/server/pkg/syncx" + "github.com/perfect-panel/server/pkg/timex" ) type limitedExecutor struct { diff --git a/pkg/logger/limitedexecutor_test.go b/pkg/logger/limitedexecutor_test.go index 34af915..eb365e9 100644 --- a/pkg/logger/limitedexecutor_test.go +++ b/pkg/logger/limitedexecutor_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/perfect-panel/ppanel-server/pkg/timex" + "github.com/perfect-panel/server/pkg/timex" "github.com/stretchr/testify/assert" ) diff --git a/pkg/logger/logtest/logtest.go b/pkg/logger/logtest/logtest.go index 779ab22..4f7c03f 100644 --- a/pkg/logger/logtest/logtest.go +++ b/pkg/logger/logtest/logtest.go @@ -6,7 +6,7 @@ import ( "io" "testing" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" ) type Buffer struct { diff --git a/pkg/logger/logtest/logtest_test.go b/pkg/logger/logtest/logtest_test.go index e98aed3..953fec2 100644 --- a/pkg/logger/logtest/logtest_test.go +++ b/pkg/logger/logtest/logtest_test.go @@ -4,7 +4,7 @@ import ( "errors" "testing" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" "github.com/stretchr/testify/assert" ) diff --git a/pkg/logger/read.go b/pkg/logger/read.go new file mode 100644 index 0000000..fc81e56 --- /dev/null +++ b/pkg/logger/read.go @@ -0,0 +1,98 @@ +package logger + +import ( + "bufio" + "fmt" + "io" + "os" +) + +func ReadLastNLines(path string, n int) ([]string, error) { + // Open the file + file, err := os.Open(fmt.Sprintf("%s/%s", path, accessFilename)) + if err != nil { + return nil, err + } + defer file.Close() + + // Get file size + fileInfo, err := file.Stat() + if err != nil { + return nil, err + } + fileSize := fileInfo.Size() + + // If file is empty, return empty slice + if fileSize == 0 { + return []string{}, nil + } + + // Buffer for reading + bufferSize := int64(4096) + if bufferSize > fileSize { + bufferSize = fileSize + } + buffer := make([]byte, bufferSize) + + // Start reading from the end + position := fileSize + lines := make([]string, 0, n) + lineCount := 0 + + for lineCount < n && position > 0 { + // How much to read + readSize := bufferSize + if position < bufferSize { + readSize = position + } + position -= readSize + + // Read chunk from position + _, err := file.Seek(position, io.SeekStart) + if err != nil { + return nil, err + } + + _, err = file.Read(buffer[:readSize]) + if err != nil { + return nil, err + } + + // Count newlines in reverse + for i := readSize - 1; i >= 0; i-- { + if buffer[i] == '\n' { + lineCount++ + if lineCount > n { + // We found more than n lines + // Need to adjust position to read only last n lines + position += int64(i) + 1 + break + } + } + } + } + + // If we couldn't find n lines, start from beginning + if position < 0 { + position = 0 + } + + // Seek to the position where we want to start reading + _, err = file.Seek(position, io.SeekStart) + if err != nil { + return nil, err + } + + // Read lines from position to end + scanner := bufio.NewScanner(file) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + // Check if we need to trim + if len(lines) > n { + lines = lines[len(lines)-n:] + } + + return lines, scanner.Err() +} diff --git a/pkg/logger/read_test.go b/pkg/logger/read_test.go new file mode 100644 index 0000000..527d32d --- /dev/null +++ b/pkg/logger/read_test.go @@ -0,0 +1,16 @@ +package logger + +import ( + "testing" +) + +func TestReadLastNLines(t *testing.T) { + t.Skipf("skip this test until this test fails") + lines, err := ReadLastNLines("/Users/tension/code/ppanel/server/logs", 10) + if err != nil { + t.Fatalf("Error reading last N lines: %v", err) + } + for i, line := range lines { + t.Logf("Line %d: %s", i, line) + } +} diff --git a/pkg/logger/richlogger.go b/pkg/logger/richlogger.go index a949510..510a4a0 100644 --- a/pkg/logger/richlogger.go +++ b/pkg/logger/richlogger.go @@ -5,9 +5,9 @@ import ( "fmt" "time" - "github.com/perfect-panel/ppanel-server/internal/trace" + "github.com/perfect-panel/server/internal/trace" - "github.com/perfect-panel/ppanel-server/pkg/timex" + "github.com/perfect-panel/server/pkg/timex" ) // WithCallerSkip returns a Logger with given caller skip. diff --git a/pkg/logger/rotatelogger.go b/pkg/logger/rotatelogger.go index 94411f6..04dffb0 100644 --- a/pkg/logger/rotatelogger.go +++ b/pkg/logger/rotatelogger.go @@ -13,8 +13,8 @@ import ( "sync" "time" - "github.com/perfect-panel/ppanel-server/pkg/fs" - "github.com/perfect-panel/ppanel-server/pkg/lang" + "github.com/perfect-panel/server/pkg/fs" + "github.com/perfect-panel/server/pkg/lang" ) const ( diff --git a/pkg/logger/rotatelogger_test.go b/pkg/logger/rotatelogger_test.go index 763dce4..c2b5be1 100644 --- a/pkg/logger/rotatelogger_test.go +++ b/pkg/logger/rotatelogger_test.go @@ -11,9 +11,9 @@ import ( "testing" "time" - "github.com/perfect-panel/ppanel-server/pkg/random" + "github.com/perfect-panel/server/pkg/random" - "github.com/perfect-panel/ppanel-server/pkg/fs" + "github.com/perfect-panel/server/pkg/fs" "github.com/stretchr/testify/assert" ) diff --git a/pkg/logger/vars.go b/pkg/logger/vars.go index f43c526..f23f083 100644 --- a/pkg/logger/vars.go +++ b/pkg/logger/vars.go @@ -3,7 +3,7 @@ package logger import ( "errors" - "github.com/perfect-panel/ppanel-server/pkg/syncx" + "github.com/perfect-panel/server/pkg/syncx" ) const ( diff --git a/pkg/logger/writer.go b/pkg/logger/writer.go index 4f7f97d..6d7658b 100644 --- a/pkg/logger/writer.go +++ b/pkg/logger/writer.go @@ -12,8 +12,8 @@ import ( "sync/atomic" fatihcolor "github.com/fatih/color" - "github.com/perfect-panel/ppanel-server/pkg/color" - "github.com/perfect-panel/ppanel-server/pkg/errorx" + "github.com/perfect-panel/server/pkg/color" + "github.com/perfect-panel/server/pkg/errorx" ) type ( diff --git a/pkg/nodeMultiplier/manage_test.go b/pkg/nodeMultiplier/manage_test.go index 789dae9..881db55 100644 --- a/pkg/nodeMultiplier/manage_test.go +++ b/pkg/nodeMultiplier/manage_test.go @@ -8,10 +8,15 @@ import ( func TestNewNodeMultiplierManager(t *testing.T) { periods := []TimePeriod{ { - StartTime: "23:00", - EndTime: "1:59", + StartTime: "23:00.000", + EndTime: "1:59.000", Multiplier: 1.2, }, + { + StartTime: "12:00.000", + EndTime: "13:59.000", + Multiplier: 0.5, + }, } m := NewNodeMultiplierManager(periods) if len(m.Periods) != 1 { diff --git a/pkg/nodeMultiplier/manager.go b/pkg/nodeMultiplier/manager.go index 7f9e687..7cb6d02 100644 --- a/pkg/nodeMultiplier/manager.go +++ b/pkg/nodeMultiplier/manager.go @@ -28,8 +28,8 @@ func (m *Manager) GetMultiplier(current time.Time) float32 { } func (m *Manager) isInTimePeriod(current time.Time, start, end string) bool { - startTime, _ := time.Parse("15:04", start) - endTime, _ := time.Parse("15:04", end) + startTime, _ := time.Parse("15:04.000", start) + endTime, _ := time.Parse("15:04.000", end) currentTime := time.Date(0, 1, 1, current.Hour(), current.Minute(), 0, 0, time.UTC) startTimeFormatted := time.Date(0, 1, 1, startTime.Hour(), startTime.Minute(), 0, 0, time.UTC) diff --git a/pkg/oauth/apple/apple.html b/pkg/oauth/apple/apple.html index aa77fc1..2bfe37d 100644 --- a/pkg/oauth/apple/apple.html +++ b/pkg/oauth/apple/apple.html @@ -11,7 +11,7 @@ AppleID.auth.init({ clientId: 'web.jiashus.com', // 替换为你的服务 ID scope: 'name email', // 可选,授权范围 - redirectURI: 'https://test.muran.org:8443/auth/apple/callback', // 替换为你的回调 URL + redirectURI: 'https://test.ppanel.dev:8443/auth/apple/callback', // 替换为你的回调 URL state: 'optional-csrf-token', // 可选 usePopup: false // 可选,是否使用弹窗方式 }); diff --git a/pkg/oauth/apple/apple_test.go b/pkg/oauth/apple/apple_test.go index 511bc6f..eec19e5 100644 --- a/pkg/oauth/apple/apple_test.go +++ b/pkg/oauth/apple/apple_test.go @@ -39,7 +39,7 @@ func handleAppleCallBack(ctx context.Context, request CallbackRequest) { ClientID: ClientID, KeyID: KeyID, ClientSecret: ClientSecret, - RedirectURI: "https://test.muran.org:8443/auth/apple/callback", + RedirectURI: "https://test.ppanel.dev:8443/auth/apple/callback", }) if err != nil { fmt.Println("error creating apple client: " + err.Error()) diff --git a/pkg/oauth/google/google.go b/pkg/oauth/google/google.go index 1d5e724..e277b3f 100644 --- a/pkg/oauth/google/google.go +++ b/pkg/oauth/google/google.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" "golang.org/x/oauth2" "golang.org/x/oauth2/google" ) diff --git a/pkg/orm/mysql.go b/pkg/orm/mysql.go index 8f7584f..07920e4 100644 --- a/pkg/orm/mysql.go +++ b/pkg/orm/mysql.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" "gorm.io/driver/mysql" "gorm.io/gorm" diff --git a/pkg/orm/tool_test.go b/pkg/orm/tool_test.go index 6bdb5cd..d415bbd 100644 --- a/pkg/orm/tool_test.go +++ b/pkg/orm/tool_test.go @@ -1,6 +1,13 @@ package orm -import "testing" +import ( + "testing" + + "github.com/perfect-panel/server/internal/model/task" + + "gorm.io/driver/mysql" + "gorm.io/gorm" +) func TestParseDSN(t *testing.T) { dsn := "root:mylove520@tcp(localhost:3306)/vpnboard" @@ -16,3 +23,18 @@ func TestPing(t *testing.T) { status := Ping(dsn) t.Log(status) } + +func TestMysql(t *testing.T) { + db, err := gorm.Open(mysql.New(mysql.Config{ + DSN: "root:mylove520@tcp(localhost:3306)/vpnboard", + })) + if err != nil { + t.Fatalf("Failed to connect to MySQL: %v", err) + } + err = db.Migrator().AutoMigrate(&task.Task{}) + if err != nil { + t.Fatalf("Failed to auto migrate: %v", err) + return + } + t.Log("MySQL connection and migration successful") +} diff --git a/pkg/payment/alipay/alipay.go b/pkg/payment/alipay/alipay.go index ad6c40e..db34a22 100644 --- a/pkg/payment/alipay/alipay.go +++ b/pkg/payment/alipay/alipay.go @@ -4,8 +4,8 @@ import ( "context" "net/url" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/tool" "github.com/pkg/errors" "github.com/smartwalle/alipay/v3" ) diff --git a/pkg/payment/epay/epay.go b/pkg/payment/epay/epay.go index 1a7e110..ae1315e 100644 --- a/pkg/payment/epay/epay.go +++ b/pkg/payment/epay/epay.go @@ -9,8 +9,8 @@ import ( "strings" "time" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/tool" ) type Client struct { diff --git a/pkg/payment/payssion/payssion.go b/pkg/payment/payssion/payssion.go deleted file mode 100644 index fa30293..0000000 --- a/pkg/payment/payssion/payssion.go +++ /dev/null @@ -1,127 +0,0 @@ -package payssion - -import ( - "bytes" - "encoding/json" - "fmt" - "github.com/perfect-panel/ppanel-server/pkg/md5" - "github.com/pkg/errors" - "go.uber.org/zap" - "io" - "log" - "net/http" -) - -type Order struct { - Name string - OrderNo string - Amount float64 - NotifyUrl string - ReturnUrl string -} - -type Client struct { - Name string - ApiKey string - SecretKey string - QueryUrl string - CreateUrl string - PmId string - Currency string -} - -func NewClient(apiKey string, secretKey, pmId, currency, queryUrl, createUrl string) *Client { - return &Client{ - ApiKey: apiKey, - SecretKey: secretKey, - PmId: pmId, - Currency: currency, - QueryUrl: queryUrl, - CreateUrl: createUrl, - } -} - -func (c *Client) CreateOrder(order Order) (string, error) { - content := fmt.Sprintf("%s|%s|%.2f|%s|%s|%s", c.ApiKey, c.PmId, order.Amount, "USD", order.OrderNo, c.SecretKey) - sign := md5.Sign(content) - params := map[string]string{ - "api_key": c.ApiKey, - "pm_id": c.PmId, - "amount": fmt.Sprintf("%.2f", order.Amount), - "currency": "USD", - "description": "shop", - "order_id": order.OrderNo, - "api_sig": sign, - "return_url": order.ReturnUrl, - } - marshal, _ := json.Marshal(params) - resp, err := http.Post(c.CreateUrl, "application/json", bytes.NewBuffer(marshal)) - if err != nil { - return "", err - } - all, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } - log.Println(string(all)) - result := make(map[string]interface{}) - err = json.Unmarshal(all, &result) - if err != nil { - return "", err - } - result_code := result["result_code"] - if fmt.Sprintf("%v", result_code) != "200" { - return "", errors.New(result["description"].(string)) - } - url := result["redirect_url"] - if url == nil { - return "", errors.New(string(all)) - } - return url.(string), nil -} - -func (c *Client) QueryOrder(orderNo string) (queryResult *QueryResult, err error) { - content := fmt.Sprintf("%s|%s|%s", c.ApiKey, orderNo, c.SecretKey) - sign := md5.Sign(content) - params := map[string]string{ - "api_key": c.ApiKey, - "order_id": orderNo, - "api_sig": sign, - } - marshal, _ := json.Marshal(params) - resp, err := http.Post(c.QueryUrl, "application/json", bytes.NewBuffer(marshal)) - if err != nil { - return nil, err - } - all, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - zap.S().Infof("Payssion QueryOrderDetail result: %s", string(all)) - err = json.Unmarshal(all, &queryResult) - return queryResult, err -} - -type QueryResult struct { - Transaction struct { - TransactionID string `json:"transaction_id"` - Description string `json:"description"` - AppName string `json:"app_name"` - PmID string `json:"pm_id"` - Amount string `json:"amount"` - Currency string `json:"currency"` - OrderID string `json:"order_id"` - Paid string `json:"paid"` - Net string `json:"net"` - State string `json:"state"` - Fee string `json:"fee"` - Refund string `json:"refund"` - RefundFee string `json:"refund_fee"` - Created int64 `json:"created"` - Updated int64 `json:"updated"` - Fees string `json:"fees"` - FeesAdd string `json:"fees_add"` - RefundFees string `json:"refund_fees"` - } `json:"transaction"` - ResultCode int64 `json:"result_code"` -} diff --git a/pkg/payment/payssion/payssion_test.go b/pkg/payment/payssion/payssion_test.go deleted file mode 100644 index 8f5ae34..0000000 --- a/pkg/payment/payssion/payssion_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package payssion - -import ( - "fmt" - "testing" -) - -func TestCreateOrder(t *testing.T) { - client := Client{ - ApiKey: "", - PmId: "", - SecretKey: "", - QueryUrl: "http://sandbox.payssion.com/api/v1/payment/getDetail", - CreateUrl: "http://sandbox.payssion.com/api/v1/payments", - } - order := Order{ - Name: "shop", - OrderNo: "123", - Amount: 1000, - } - createOrder, err := client.CreateOrder(order) - if err != nil { - t.Error(err) - return - } - fmt.Println(createOrder) - queryOrder, err := client.QueryOrder(order.OrderNo) - if err != nil { - t.Error(err) - } - fmt.Println(queryOrder.Transaction.State) -} diff --git a/pkg/payment/platform.go b/pkg/payment/platform.go index b749c83..42b8815 100644 --- a/pkg/payment/platform.go +++ b/pkg/payment/platform.go @@ -1,6 +1,6 @@ package payment -import "github.com/perfect-panel/ppanel-server/internal/types" +import "github.com/perfect-panel/server/internal/types" type Platform int @@ -9,15 +9,15 @@ const ( AlipayF2F EPay Balance - Payssion - UNSUPPORTED + CryptoSaaS + UNSUPPORTED Platform = -1 ) var platformNames = map[string]Platform{ + "CryptoSaaS": CryptoSaaS, "Stripe": Stripe, "AlipayF2F": AlipayF2F, "EPay": EPay, - "Payssion": Payssion, "balance": Balance, "unsupported": UNSUPPORTED, } @@ -71,15 +71,12 @@ func GetSupportedPlatforms() []types.PlatformInfo { }, }, { - Platform: Payssion.String(), - PlatformUrl: "", + Platform: CryptoSaaS.String(), + PlatformUrl: "https://t.me/CryptoSaaSBot", PlatformFieldDescription: map[string]string{ - "api_key": "api_key", - "secret_key": "secret_key", - "pm_id": "pm_id", - "currency": "currency", - "create_url": "Create URL", - "query_url": "Query URL", + "endpoint": "API Endpoint", + "account_id": "Account ID", + "secret_key": "Secret Key", }, }, } diff --git a/pkg/payment/stripe/stripe.go b/pkg/payment/stripe/stripe.go index 4bd5080..d0f4af6 100644 --- a/pkg/payment/stripe/stripe.go +++ b/pkg/payment/stripe/stripe.go @@ -5,7 +5,9 @@ import ( "fmt" "strconv" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/stripe/stripe-go/v81/webhookendpoint" + + "github.com/perfect-panel/server/pkg/logger" "github.com/stripe/stripe-go/v81" "github.com/stripe/stripe-go/v81/customer" "github.com/stripe/stripe-go/v81/ephemeralkey" @@ -193,3 +195,16 @@ func (c *Client) RetrievePaymentMethod(id string) (*stripe.PaymentMethod, error) stripe.Key = c.SecretKey return paymentmethod.Get(id, nil) } + +// CreateWebhookEndpoint 创建 webhook endpoint +func (c *Client) CreateWebhookEndpoint(url string) (*stripe.WebhookEndpoint, error) { + stripe.Key = c.SecretKey + params := &stripe.WebhookEndpointParams{ + URL: stripe.String(url), + EnabledEvents: []*string{ + stripe.String("payment_intent.succeeded"), + stripe.String("payment_intent.payment_failed"), + }, + } + return webhookendpoint.New(params) +} diff --git a/pkg/payment/stripe/stripe_test.go b/pkg/payment/stripe/stripe_test.go index 872b9e9..419b758 100644 --- a/pkg/payment/stripe/stripe_test.go +++ b/pkg/payment/stripe/stripe_test.go @@ -20,7 +20,7 @@ func TestStripeAlipay(t *testing.T) { } user := User{ UserId: 1, - Email: "tension@muran.org", + Email: "tension@ppanel.dev", } result, err := client.CreatePaymentSheet(&order, &user) if err != nil { @@ -45,7 +45,7 @@ func TestStripeWechat(t *testing.T) { } user := User{ UserId: 1, - Email: "tension@muran.org", + Email: "tension@ppanel.dev", } result, err := client.CreatePaymentSheet(&order, &user) if err != nil { diff --git a/pkg/proc/shutdown.go b/pkg/proc/shutdown.go index d08b466..c2283a3 100644 --- a/pkg/proc/shutdown.go +++ b/pkg/proc/shutdown.go @@ -9,7 +9,7 @@ import ( "syscall" "time" - "github.com/perfect-panel/ppanel-server/pkg/threading" + "github.com/perfect-panel/server/pkg/threading" ) const ( diff --git a/pkg/random/RandomKey_test.go b/pkg/random/RandomKey_test.go index a5f23fd..244054d 100644 --- a/pkg/random/RandomKey_test.go +++ b/pkg/random/RandomKey_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/perfect-panel/ppanel-server/pkg/snowflake" + "github.com/perfect-panel/server/pkg/snowflake" "github.com/stretchr/testify/assert" ) diff --git a/pkg/rescue/recover.go b/pkg/rescue/recover.go index 3138cb6..8e6ffd8 100644 --- a/pkg/rescue/recover.go +++ b/pkg/rescue/recover.go @@ -5,7 +5,7 @@ import ( "log" "runtime/debug" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" ) // Recover is used with defer to do cleanup on panics. diff --git a/pkg/result/httpResult.go b/pkg/result/httpResult.go index 728115a..f5a125a 100644 --- a/pkg/result/httpResult.go +++ b/pkg/result/httpResult.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" "github.com/pkg/errors" - "github.com/perfect-panel/ppanel-server/pkg/xerr" + "github.com/perfect-panel/server/pkg/xerr" ) // HttpResult HTTP Result diff --git a/pkg/service/service.go b/pkg/service/service.go index 668975b..58a7d94 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -3,8 +3,8 @@ package service import ( "sync" - "github.com/perfect-panel/ppanel-server/pkg/proc" - "github.com/perfect-panel/ppanel-server/pkg/threading" + "github.com/perfect-panel/server/pkg/proc" + "github.com/perfect-panel/server/pkg/threading" ) type ( diff --git a/pkg/service/servicegroup_test.go b/pkg/service/servicegroup_test.go index 8ade90a..aaf683e 100644 --- a/pkg/service/servicegroup_test.go +++ b/pkg/service/servicegroup_test.go @@ -4,7 +4,7 @@ import ( "sync" "testing" - "github.com/perfect-panel/ppanel-server/pkg/proc" + "github.com/perfect-panel/server/pkg/proc" "github.com/stretchr/testify/assert" ) diff --git a/pkg/sms/abosend/abosend.go b/pkg/sms/abosend/abosend.go index 4701b79..1291bf0 100644 --- a/pkg/sms/abosend/abosend.go +++ b/pkg/sms/abosend/abosend.go @@ -5,9 +5,9 @@ import ( "fmt" "github.com/go-resty/resty/v2" - "github.com/perfect-panel/ppanel-server/pkg/random" - "github.com/perfect-panel/ppanel-server/pkg/templatex" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/pkg/random" + "github.com/perfect-panel/server/pkg/templatex" + "github.com/perfect-panel/server/pkg/tool" ) const BaseURL = "https://smsapi.abosend.com" diff --git a/pkg/sms/alibabacloud/alibabacloud.go b/pkg/sms/alibabacloud/alibabacloud.go index 992772f..03e71f4 100644 --- a/pkg/sms/alibabacloud/alibabacloud.go +++ b/pkg/sms/alibabacloud/alibabacloud.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" openapi "github.com/alibabacloud-go/darabonba-openapi/client" dysmsapi "github.com/alibabacloud-go/dysmsapi-20170525/v2/client" diff --git a/pkg/sms/platform.go b/pkg/sms/platform.go index ef22cde..f06a7b9 100644 --- a/pkg/sms/platform.go +++ b/pkg/sms/platform.go @@ -1,6 +1,6 @@ package sms -import "github.com/perfect-panel/ppanel-server/internal/types" +import "github.com/perfect-panel/server/internal/types" type Platform int diff --git a/pkg/sms/sender.go b/pkg/sms/sender.go index 30c2a72..a46e34f 100644 --- a/pkg/sms/sender.go +++ b/pkg/sms/sender.go @@ -5,10 +5,10 @@ import ( "fmt" "log" - "github.com/perfect-panel/ppanel-server/pkg/sms/abosend" - "github.com/perfect-panel/ppanel-server/pkg/sms/alibabacloud" - "github.com/perfect-panel/ppanel-server/pkg/sms/smsbao" - "github.com/perfect-panel/ppanel-server/pkg/sms/twilio" + "github.com/perfect-panel/server/pkg/sms/abosend" + "github.com/perfect-panel/server/pkg/sms/alibabacloud" + "github.com/perfect-panel/server/pkg/sms/smsbao" + "github.com/perfect-panel/server/pkg/sms/twilio" ) type Sender interface { diff --git a/pkg/sms/smsbao/smsbao.go b/pkg/sms/smsbao/smsbao.go index 20a9f4b..0cff2c7 100644 --- a/pkg/sms/smsbao/smsbao.go +++ b/pkg/sms/smsbao/smsbao.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/go-resty/resty/v2" - "github.com/perfect-panel/ppanel-server/pkg/templatex" - "github.com/perfect-panel/ppanel-server/pkg/tool" + "github.com/perfect-panel/server/pkg/templatex" + "github.com/perfect-panel/server/pkg/tool" ) const BaseURL = "https://api.smsbao.com" diff --git a/pkg/sms/twilio/twilio.go b/pkg/sms/twilio/twilio.go index bb5252d..51ea6a3 100644 --- a/pkg/sms/twilio/twilio.go +++ b/pkg/sms/twilio/twilio.go @@ -3,8 +3,8 @@ package twilio import ( "fmt" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/templatex" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/templatex" "github.com/twilio/twilio-go" twilioApi "github.com/twilio/twilio-go/rest/api/v2010" ) diff --git a/pkg/syncx/cond.go b/pkg/syncx/cond.go index 3c82843..e35e455 100644 --- a/pkg/syncx/cond.go +++ b/pkg/syncx/cond.go @@ -3,8 +3,8 @@ package syncx import ( "time" - "github.com/perfect-panel/ppanel-server/pkg/lang" - "github.com/perfect-panel/ppanel-server/pkg/timex" + "github.com/perfect-panel/server/pkg/lang" + "github.com/perfect-panel/server/pkg/timex" ) // A Cond is used to wait for conditions. diff --git a/pkg/syncx/donechan.go b/pkg/syncx/donechan.go index c748f53..ebe7ef4 100644 --- a/pkg/syncx/donechan.go +++ b/pkg/syncx/donechan.go @@ -3,7 +3,7 @@ package syncx import ( "sync" - "github.com/perfect-panel/ppanel-server/pkg/lang" + "github.com/perfect-panel/server/pkg/lang" ) // A DoneChan is used as a channel that can be closed multiple times and wait for done. diff --git a/pkg/syncx/immutableresource.go b/pkg/syncx/immutableresource.go index d207d07..8122c44 100644 --- a/pkg/syncx/immutableresource.go +++ b/pkg/syncx/immutableresource.go @@ -4,7 +4,7 @@ import ( "sync" "time" - "github.com/perfect-panel/ppanel-server/pkg/timex" + "github.com/perfect-panel/server/pkg/timex" ) const defaultRefreshInterval = time.Second diff --git a/pkg/syncx/limit.go b/pkg/syncx/limit.go index 5c6ce9e..d3209ba 100644 --- a/pkg/syncx/limit.go +++ b/pkg/syncx/limit.go @@ -3,7 +3,7 @@ package syncx import ( "errors" - "github.com/perfect-panel/ppanel-server/pkg/lang" + "github.com/perfect-panel/server/pkg/lang" ) // ErrLimitReturn indicates that the more than borrowed elements were returned. diff --git a/pkg/syncx/pool.go b/pkg/syncx/pool.go index 2bccacb..489b98c 100644 --- a/pkg/syncx/pool.go +++ b/pkg/syncx/pool.go @@ -4,7 +4,7 @@ import ( "sync" "time" - "github.com/perfect-panel/ppanel-server/pkg/timex" + "github.com/perfect-panel/server/pkg/timex" ) type ( diff --git a/pkg/syncx/pool_test.go b/pkg/syncx/pool_test.go index 35ab59d..5bdb47c 100644 --- a/pkg/syncx/pool_test.go +++ b/pkg/syncx/pool_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/perfect-panel/ppanel-server/pkg/lang" + "github.com/perfect-panel/server/pkg/lang" "github.com/stretchr/testify/assert" ) diff --git a/pkg/syncx/resourcemanager.go b/pkg/syncx/resourcemanager.go index ae4ae47..0c8dc5b 100644 --- a/pkg/syncx/resourcemanager.go +++ b/pkg/syncx/resourcemanager.go @@ -4,7 +4,7 @@ import ( "io" "sync" - "github.com/perfect-panel/ppanel-server/pkg/errorx" + "github.com/perfect-panel/server/pkg/errorx" ) // A ResourceManager is a manager that used to manage resources. diff --git a/pkg/syncx/spinlock_test.go b/pkg/syncx/spinlock_test.go index 026617c..9bcff23 100644 --- a/pkg/syncx/spinlock_test.go +++ b/pkg/syncx/spinlock_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/perfect-panel/ppanel-server/pkg/lang" + "github.com/perfect-panel/server/pkg/lang" "github.com/stretchr/testify/assert" ) diff --git a/pkg/threading/routines.go b/pkg/threading/routines.go index f7508a9..58352c7 100644 --- a/pkg/threading/routines.go +++ b/pkg/threading/routines.go @@ -6,7 +6,7 @@ import ( "runtime" "strconv" - "github.com/perfect-panel/ppanel-server/pkg/rescue" + "github.com/perfect-panel/server/pkg/rescue" ) // GoSafe runs the given fn using another goroutine, recovers if fn panics. diff --git a/pkg/timex/ticker.go b/pkg/timex/ticker.go index c0ddf96..df4913f 100644 --- a/pkg/timex/ticker.go +++ b/pkg/timex/ticker.go @@ -4,7 +4,7 @@ import ( "errors" "time" - "github.com/perfect-panel/ppanel-server/pkg/lang" + "github.com/perfect-panel/server/pkg/lang" ) // errTimeout indicates a timeout. diff --git a/pkg/tool/cipher_test.go b/pkg/tool/cipher_test.go index fb0f592..4bfc07e 100644 --- a/pkg/tool/cipher_test.go +++ b/pkg/tool/cipher_test.go @@ -5,6 +5,7 @@ import ( ) func TestGenerateCipher(t *testing.T) { - pwd := GenerateCipher("serverKey", 128) + pwd := GenerateCipher("", 16) t.Logf("pwd: %s", pwd) + t.Logf("pwd length: %d", len(pwd)) } diff --git a/pkg/tool/convert.go b/pkg/tool/convert.go index fa93055..0079620 100644 --- a/pkg/tool/convert.go +++ b/pkg/tool/convert.go @@ -1,6 +1,7 @@ package tool import ( + "encoding/json" "fmt" "reflect" "strconv" @@ -26,6 +27,16 @@ func ConvertValueToString(value reflect.Value) string { default: return "" } + case reflect.Struct, reflect.Map, reflect.Slice, reflect.Array: + bytes, err := json.Marshal(value.Interface()) + if err != nil { + fmt.Println("Error marshaling struct:", err.Error()) + return "" + } + if string(bytes) == "null" { + return "" + } + return string(bytes) default: return "" } diff --git a/pkg/tool/copy.go b/pkg/tool/copy.go index 6464426..7d707c1 100644 --- a/pkg/tool/copy.go +++ b/pkg/tool/copy.go @@ -10,20 +10,31 @@ import ( "github.com/jinzhu/copier" "github.com/pkg/errors" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" ) -func DeepCopy[T, K interface{}](destStruct T, srcStruct K) T { +// CopyOption 定义复制选项的函数类型 +type CopyOption func(*copier.Option) + +// CopyWithIgnoreEmpty 设置是否忽略空值 +func CopyWithIgnoreEmpty(ignoreEmpty bool) CopyOption { + return func(o *copier.Option) { + o.IgnoreEmpty = ignoreEmpty + } +} + +func DeepCopy[T, K any](destStruct T, srcStruct K, opts ...CopyOption) T { var dst = destStruct var src = srcStruct - _ = copier.CopyWithOption(dst, src, copier.Option{ + + option := copier.Option{ DeepCopy: true, IgnoreEmpty: true, Converters: []copier.TypeConverter{ { SrcType: time.Time{}, DstType: constant.Int64, - Fn: func(src interface{}) (interface{}, error) { + Fn: func(src any) (any, error) { s, ok := src.(time.Time) if !ok { return nil, errors.New("src type not matching") @@ -32,13 +43,21 @@ func DeepCopy[T, K interface{}](destStruct T, srcStruct K) T { }, }, }, - }) + } + + for _, opt := range opts { + opt(&option) + } + + _ = copier.CopyWithOption(dst, src, option) return dst } -func ShallowCopy[T, K interface{}](destStruct T, srcStruct K) T { + +func ShallowCopy[T, K interface{}](destStruct T, srcStruct K, opts ...CopyOption) T { var dst = destStruct var src = srcStruct - _ = copier.CopyWithOption(dst, src, copier.Option{ + + option := copier.Option{ IgnoreEmpty: true, Converters: []copier.TypeConverter{ { @@ -46,7 +65,6 @@ func ShallowCopy[T, K interface{}](destStruct T, srcStruct K) T { DstType: constant.Int64, Fn: func(src interface{}) (interface{}, error) { s, ok := src.(time.Time) - if !ok { return nil, errors.New("src type not matching") } @@ -54,7 +72,13 @@ func ShallowCopy[T, K interface{}](destStruct T, srcStruct K) T { }, }, }, - }) + } + + for _, opt := range opts { + opt(&option) + } + + _ = copier.CopyWithOption(dst, src, option) return dst } diff --git a/pkg/tool/encryption_test.go b/pkg/tool/encryption_test.go index 45e0a18..8841072 100644 --- a/pkg/tool/encryption_test.go +++ b/pkg/tool/encryption_test.go @@ -3,5 +3,5 @@ package tool import "testing" func TestEncodePassWord(t *testing.T) { - t.Logf("EncodePassWord: %v", EncodePassWord("")) + t.Logf("EncodePassWord: %v", EncodePassWord("password")) } diff --git a/pkg/tool/slice.go b/pkg/tool/slice.go index 337b357..3797878 100644 --- a/pkg/tool/slice.go +++ b/pkg/tool/slice.go @@ -5,7 +5,7 @@ import ( "strconv" "strings" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" ) func Int64SliceToStringSlice(slice []int64) []string { @@ -59,6 +59,7 @@ func Int64SliceToString(intSlice []int64) string { // string slice to string func StringSliceToString(stringSlice []string) string { + stringSlice = RemoveDuplicateElements(stringSlice...) return strings.Join(stringSlice, ",") } @@ -128,3 +129,14 @@ func Contains[T comparable](slice []T, target T) bool { } return false } + +// RemoveStringElement 移除指定元素 +func RemoveStringElement(arr []string, element ...string) []string { + var result []string + for _, str := range arr { + if !Contains(element, str) { + result = append(result, str) + } + } + return result +} diff --git a/pkg/tool/sliceReflectToStruct.go b/pkg/tool/sliceReflectToStruct.go index 644e91b..8458a3c 100644 --- a/pkg/tool/sliceReflectToStruct.go +++ b/pkg/tool/sliceReflectToStruct.go @@ -5,7 +5,7 @@ import ( "strconv" "github.com/goccy/go-json" - "github.com/perfect-panel/ppanel-server/internal/model/system" + "github.com/perfect-panel/server/internal/model/system" ) func SystemConfigSliceReflectToStruct(slice []*system.System, structType any) { diff --git a/pkg/tool/time.go b/pkg/tool/time.go index 67a2535..31c6883 100644 --- a/pkg/tool/time.go +++ b/pkg/tool/time.go @@ -3,7 +3,7 @@ package tool import ( "time" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" ) func AddTime(unit string, quantity int64, baseTime ...time.Time) time.Time { @@ -138,3 +138,16 @@ func YearDiff(startTime, endTime time.Time) int { } return yearDiff } + +func DayDiff(startTime, endTime time.Time) int64 { + // 计算时间差 + duration := endTime.Sub(startTime) + return int64(duration.Hours() / 24) // 转换为整天数 +} + +// HourDiff 计算两个时间点之间的小时差 +func HourDiff(startTime, endTime time.Time) int64 { + // 计算时间差 + duration := endTime.Sub(startTime) + return int64(duration.Hours()) // 返回小时数,可能包含小数部分 +} diff --git a/pkg/tool/version_test.go b/pkg/tool/version_test.go index 2878371..02d8920 100644 --- a/pkg/tool/version_test.go +++ b/pkg/tool/version_test.go @@ -3,7 +3,7 @@ package tool import ( "testing" - "github.com/perfect-panel/ppanel-server/pkg/constant" + "github.com/perfect-panel/server/pkg/constant" ) func TestExtractVersionNumber(t *testing.T) { diff --git a/pkg/trace/agent.go b/pkg/trace/agent.go index c66f8c4..a8bf870 100644 --- a/pkg/trace/agent.go +++ b/pkg/trace/agent.go @@ -18,8 +18,8 @@ import ( sdktrace "go.opentelemetry.io/otel/sdk/trace" - "github.com/perfect-panel/ppanel-server/pkg/lang" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/lang" + "github.com/perfect-panel/server/pkg/logger" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" ) diff --git a/pkg/trace/agent_test.go b/pkg/trace/agent_test.go index 7176419..02e8e5f 100644 --- a/pkg/trace/agent_test.go +++ b/pkg/trace/agent_test.go @@ -3,7 +3,7 @@ package trace import ( "testing" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" "github.com/stretchr/testify/assert" ) diff --git a/pkg/trace/utils.go b/pkg/trace/utils.go index 2756284..beb3a4f 100644 --- a/pkg/trace/utils.go +++ b/pkg/trace/utils.go @@ -5,7 +5,7 @@ import ( "net" "strings" - ptrace "github.com/perfect-panel/ppanel-server/internal/trace" + ptrace "github.com/perfect-panel/server/internal/trace" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" diff --git a/pkg/uuidx/uuid.go b/pkg/uuidx/uuid.go index baaaf75..5573ae8 100644 --- a/pkg/uuidx/uuid.go +++ b/pkg/uuidx/uuid.go @@ -8,7 +8,7 @@ import ( "time" "github.com/gofrs/uuid/v5" - "github.com/perfect-panel/ppanel-server/pkg/random" + "github.com/perfect-panel/server/pkg/random" ) // NewUUID returns a new UUID. diff --git a/pkg/uuidx/uuid_test.go b/pkg/uuidx/uuid_test.go index ca83b9a..4ec2493 100644 --- a/pkg/uuidx/uuid_test.go +++ b/pkg/uuidx/uuid_test.go @@ -20,8 +20,8 @@ import ( "testing" "time" - "github.com/perfect-panel/ppanel-server/pkg/random" - "github.com/perfect-panel/ppanel-server/pkg/snowflake" + "github.com/perfect-panel/server/pkg/random" + "github.com/perfect-panel/server/pkg/snowflake" "github.com/gofrs/uuid/v5" ) diff --git a/pkg/xerr/errCode.go b/pkg/xerr/errCode.go index 8b238a1..64cbd6a 100644 --- a/pkg/xerr/errCode.go +++ b/pkg/xerr/errCode.go @@ -52,9 +52,10 @@ const ( //coupon error const ( - CouponNotExist uint32 = 50001 - CouponUsed uint32 = 50002 - CouponNotMatch uint32 = 50003 + CouponNotExist uint32 = 50001 // Coupon does not exist + CouponAlreadyUsed uint32 = 50002 // Coupon has already been used + CouponNotApplicable uint32 = 50003 // Coupon does not match the order or conditions + CouponInsufficientUsage uint32 = 50004 // Coupon has insufficient remaining uses ) // Subscribe diff --git a/pkg/xerr/errMsg.go b/pkg/xerr/errMsg.go index b0fa9a8..a259e54 100644 --- a/pkg/xerr/errMsg.go +++ b/pkg/xerr/errMsg.go @@ -41,9 +41,10 @@ func init() { NodeGroupNotEmpty: "Node group is not empty", //coupon error - CouponNotExist: "Coupon does not exist", - CouponUsed: "Coupon has been used", - CouponNotMatch: "Coupon does not match", + CouponNotExist: "Coupon does not exist", + CouponAlreadyUsed: "Coupon has already been used", + CouponNotApplicable: "Coupon does not match the order or conditions", + CouponInsufficientUsage: "Coupon has insufficient remaining uses", // Subscribe SubscribeExpired: "Subscribe is expired", diff --git a/ppanel.api b/ppanel.api index ca98a2f..10c83c2 100644 --- a/ppanel.api +++ b/ppanel.api @@ -27,6 +27,8 @@ import ( "apis/admin/console.api" "apis/admin/log.api" "apis/admin/ads.api" + "apis/admin/marketing.api" + "apis/admin/application.api" "apis/public/user.api" "apis/public/subscribe.api" "apis/public/order.api" @@ -35,14 +37,5 @@ import ( "apis/public/payment.api" "apis/public/document.api" "apis/public/portal.api" - "apis/app/auth.api" - "apis/app/user.api" - "apis/app/node.api" - "apis/app/ws.api" - "apis/app/order.api" - "apis/app/announcement.api" - "apis/app/payment.api" - "apis/app/document.api" - "apis/app/subscribe.api" ) diff --git a/ppanel.go b/ppanel.go index 7c8624c..a2e8110 100644 --- a/ppanel.go +++ b/ppanel.go @@ -1,6 +1,6 @@ package main -import "github.com/perfect-panel/ppanel-server/cmd" +import "github.com/perfect-panel/server/cmd" func main() { cmd.Execute() diff --git a/queue/handler/routes.go b/queue/handler/routes.go index bb315d1..edf2293 100644 --- a/queue/handler/routes.go +++ b/queue/handler/routes.go @@ -2,15 +2,16 @@ package handler import ( "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/svc" - countrylogic "github.com/perfect-panel/ppanel-server/queue/logic/country" - orderLogic "github.com/perfect-panel/ppanel-server/queue/logic/order" - smslogic "github.com/perfect-panel/ppanel-server/queue/logic/sms" - "github.com/perfect-panel/ppanel-server/queue/logic/subscription" - "github.com/perfect-panel/ppanel-server/queue/logic/traffic" - "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/svc" + countrylogic "github.com/perfect-panel/server/queue/logic/country" + orderLogic "github.com/perfect-panel/server/queue/logic/order" + smslogic "github.com/perfect-panel/server/queue/logic/sms" + "github.com/perfect-panel/server/queue/logic/subscription" + "github.com/perfect-panel/server/queue/logic/task" + "github.com/perfect-panel/server/queue/logic/traffic" + "github.com/perfect-panel/server/queue/types" - emailLogic "github.com/perfect-panel/ppanel-server/queue/logic/email" + emailLogic "github.com/perfect-panel/server/queue/logic/email" ) func RegisterHandlers(mux *asynq.ServeMux, serverCtx *svc.ServiceContext) { @@ -20,7 +21,6 @@ func RegisterHandlers(mux *asynq.ServeMux, serverCtx *svc.ServiceContext) { mux.Handle(types.ForthwithSendEmail, emailLogic.NewSendEmailLogic(serverCtx)) // Send sms task mux.Handle(types.ForthwithSendSms, smslogic.NewSendSmsLogic(serverCtx)) - // Defer close order task mux.Handle(types.DeferCloseOrder, orderLogic.NewDeferCloseOrderLogic(serverCtx)) // Forthwith activate order task @@ -34,6 +34,16 @@ func RegisterHandlers(mux *asynq.ServeMux, serverCtx *svc.ServiceContext) { // Schedule total server data mux.Handle(types.SchedulerTotalServerData, traffic.NewServerDataLogic(serverCtx)) - //定时查单 - mux.Handle(types.SchedulerCheckOrder, orderLogic.NewCheckOrderLogic(serverCtx)) + + // Schedule reset traffic + mux.Handle(types.SchedulerResetTraffic, traffic.NewResetTrafficLogic(serverCtx)) + + // ScheduledBatchSendEmail + mux.Handle(types.ScheduledBatchSendEmail, emailLogic.NewBatchEmailLogic(serverCtx)) + + // ScheduledTrafficStat + mux.Handle(types.SchedulerTrafficStat, traffic.NewStatLogic(serverCtx)) + + // ForthwithQuotaTask + mux.Handle(types.ForthwithQuotaTask, task.NewQuotaTaskLogic(serverCtx)) } diff --git a/queue/logic/country/getCountryLogic.go b/queue/logic/country/getCountryLogic.go index d3e9236..75e0f6f 100644 --- a/queue/logic/country/getCountryLogic.go +++ b/queue/logic/country/getCountryLogic.go @@ -2,14 +2,9 @@ package countrylogic import ( "context" - "encoding/json" - - "github.com/perfect-panel/ppanel-server/pkg/logger" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/ip" - "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/svc" ) type GetNodeCountryLogic struct { @@ -22,39 +17,6 @@ func NewGetNodeCountryLogic(svcCtx *svc.ServiceContext) *GetNodeCountryLogic { } } func (l *GetNodeCountryLogic) ProcessTask(ctx context.Context, task *asynq.Task) error { - var payload types.GetNodeCountry - if err := json.Unmarshal(task.Payload(), &payload); err != nil { - logger.WithContext(ctx).Error("[GetNodeCountryLogic] Unmarshal payload failed", - logger.Field("error", err.Error()), - logger.Field("payload", task.Payload()), - ) - return nil - } - serverAddr := payload.ServerAddr - resp, err := ip.GetRegionByIp(serverAddr) - if err != nil { - logger.WithContext(ctx).Error("[GetNodeCountryLogic] ", logger.Field("error", err.Error()), logger.Field("serverAddr", serverAddr)) - return nil - } - servers, err := l.svcCtx.ServerModel.FindNodeByServerAddrAndProtocol(ctx, payload.ServerAddr, payload.Protocol) - if err != nil { - logger.WithContext(ctx).Error("[GetNodeCountryLogic] FindNodeByServerAddrAnd", logger.Field("error", err.Error()), logger.Field("serverAddr", serverAddr)) - return err - } - if len(servers) == 0 { - return nil - } - for _, ser := range servers { - ser.Country = resp.Country - ser.City = resp.City - ser.Latitude = resp.Latitude - ser.Longitude = resp.Longitude - err := l.svcCtx.ServerModel.Update(ctx, ser) - if err != nil { - logger.WithContext(ctx).Error("[GetNodeCountryLogic] ", logger.Field("error", err.Error()), logger.Field("id", ser.Id)) - } - } - logger.WithContext(ctx).Info("[GetNodeCountryLogic] ", logger.Field("country", resp.Country), logger.Field("city", resp.Country)) return nil } diff --git a/queue/logic/email/batchEmailLogic.go b/queue/logic/email/batchEmailLogic.go new file mode 100644 index 0000000..2aa8123 --- /dev/null +++ b/queue/logic/email/batchEmailLogic.go @@ -0,0 +1,78 @@ +package emailLogic + +import ( + "context" + "strconv" + + "github.com/hibiken/asynq" + taskModel "github.com/perfect-panel/server/internal/model/task" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/email" + "github.com/perfect-panel/server/pkg/logger" +) + +type BatchEmailLogic struct { + svcCtx *svc.ServiceContext +} + +type ErrorInfo struct { + Error string `json:"error"` + Email string `json:"email"` + Time int64 `json:"time"` +} + +func NewBatchEmailLogic(svcCtx *svc.ServiceContext) *BatchEmailLogic { + return &BatchEmailLogic{ + svcCtx: svcCtx, + } +} + +func (l *BatchEmailLogic) ProcessTask(ctx context.Context, task *asynq.Task) error { + // 解析任务负载 + payload := task.Payload() + if len(payload) == 0 { + logger.Error("[BatchEmailLogic] ProcessTask failed: empty payload") + return asynq.SkipRetry + } + // 转换获取任务id + taskID, err := strconv.ParseInt(string(payload), 10, 64) + if err != nil { + logger.WithContext(ctx).Error("[BatchEmailLogic] ProcessTask failed: invalid task ID", + logger.Field("error", err.Error()), + logger.Field("payload", string(payload)), + ) + return asynq.SkipRetry + } + tx := l.svcCtx.DB.WithContext(ctx) + var taskInfo taskModel.Task + if err = tx.Model(&taskModel.Task{}).Where("id = ?", taskID).First(&taskInfo).Error; err != nil { + logger.WithContext(ctx).Error("[BatchEmailLogic] ProcessTask failed", + logger.Field("error", err.Error()), + logger.Field("taskID", taskID), + ) + return asynq.SkipRetry + } + + if taskInfo.Status != 0 { + logger.WithContext(ctx).Info("[BatchEmailLogic] ProcessTask skipped: task already processed", + logger.Field("taskID", taskID), + logger.Field("status", taskInfo.Status), + ) + return nil + } + + sender, err := email.NewSender(l.svcCtx.Config.Email.Platform, l.svcCtx.Config.Email.PlatformConfig, l.svcCtx.Config.Site.SiteName) + if err != nil { + logger.WithContext(ctx).Error("[BatchEmailLogic] NewSender failed", logger.Field("error", err.Error())) + return nil + } + manager := email.NewWorkerManager(l.svcCtx.DB, sender) + if manager == nil { + logger.WithContext(ctx).Error("[BatchEmailLogic] ProcessTask failed: worker manager is nil") + return asynq.SkipRetry + } + + // 添加或获取 Worker 实例 + manager.AddWorker(taskID) + return nil +} diff --git a/queue/logic/email/sendEmailLogic.go b/queue/logic/email/sendEmailLogic.go index 393a796..7a56350 100644 --- a/queue/logic/email/sendEmailLogic.go +++ b/queue/logic/email/sendEmailLogic.go @@ -1,16 +1,19 @@ package emailLogic import ( + "bytes" "context" "encoding/json" + "text/template" + "time" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/log" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/email" - "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/email" + "github.com/perfect-panel/server/queue/types" ) type SendEmailLogic struct { @@ -31,8 +34,7 @@ func (l *SendEmailLogic) ProcessTask(ctx context.Context, task *asynq.Task) erro ) return nil } - messageLog := log.MessageLog{ - Type: log.Email.String(), + messageLog := log.Message{ Platform: l.svcCtx.Config.Email.Platform, To: payload.Email, Subject: payload.Subject, @@ -43,18 +45,111 @@ func (l *SendEmailLogic) ProcessTask(ctx context.Context, task *asynq.Task) erro logger.WithContext(ctx).Error("[SendEmailLogic] NewSender failed", logger.Field("error", err.Error())) return nil } - err = sender.Send([]string{payload.Email}, payload.Subject, payload.Content) + var content string + switch payload.Type { + case types.EmailTypeVerify: + tpl, _ := template.New("verify").Parse(l.svcCtx.Config.Email.VerifyEmailTemplate) + var result bytes.Buffer + + payload.Content["Type"] = uint8(payload.Content["Type"].(float64)) + + err = tpl.Execute(&result, payload.Content) + if err != nil { + logger.WithContext(ctx).Error("[SendEmailLogic] Execute template failed", + logger.Field("error", err.Error()), + logger.Field("data", payload.Content), + ) + return nil + } + content = result.String() + case types.EmailTypeMaintenance: + tpl, _ := template.New("maintenance").Parse(l.svcCtx.Config.Email.MaintenanceEmailTemplate) + var result bytes.Buffer + err = tpl.Execute(&result, payload.Content) + if err != nil { + logger.WithContext(ctx).Error("[SendEmailLogic] Execute template failed", + logger.Field("error", err.Error()), + logger.Field("template", l.svcCtx.Config.Email.MaintenanceEmailTemplate), + logger.Field("data", payload.Content), + ) + return nil + } + content = result.String() + case types.EmailTypeExpiration: + tpl, _ := template.New("expiration").Parse(l.svcCtx.Config.Email.ExpirationEmailTemplate) + var result bytes.Buffer + err = tpl.Execute(&result, payload.Content) + if err != nil { + logger.WithContext(ctx).Error("[SendEmailLogic] Execute template failed", + logger.Field("error", err.Error()), + logger.Field("template", l.svcCtx.Config.Email.ExpirationEmailTemplate), + logger.Field("data", payload.Content), + ) + return nil + } + content = result.String() + case types.EmailTypeTrafficExceed: + tpl, _ := template.New("traffic_exceed").Parse(l.svcCtx.Config.Email.TrafficExceedEmailTemplate) + var result bytes.Buffer + err = tpl.Execute(&result, payload.Content) + if err != nil { + logger.WithContext(ctx).Error("[SendEmailLogic] Execute template failed", + logger.Field("error", err.Error()), + logger.Field("template", l.svcCtx.Config.Email.TrafficExceedEmailTemplate), + logger.Field("data", payload.Content), + ) + return nil + } + content = result.String() + case types.EmailTypeCustom: + if payload.Content == nil { + logger.WithContext(ctx).Error("[SendEmailLogic] Custom email content is empty", + logger.Field("payload", payload), + ) + return nil + } + if tpl, ok := payload.Content["content"].(string); !ok { + logger.WithContext(ctx).Error("[SendEmailLogic] Custom email content is not a string", + logger.Field("payload", payload), + ) + return nil + } else { + content = tpl + } + default: + logger.WithContext(ctx).Error("[SendEmailLogic] Unsupported email type", + logger.Field("type", payload.Type), + logger.Field("payload", payload), + ) + return nil + } + + err = sender.Send([]string{payload.Email}, payload.Subject, content) if err != nil { logger.WithContext(ctx).Error("[SendEmailLogic] Send email failed", logger.Field("error", err.Error())) return nil } messageLog.Status = 1 - if err = l.svcCtx.LogModel.InsertMessageLog(ctx, &messageLog); err != nil { - logger.WithContext(ctx).Error("[SendEmailLogic] InsertMessageLog failed", + emailLog, err := messageLog.Marshal() + if err != nil { + logger.WithContext(ctx).Error("[SendEmailLogic] Marshal message log failed", logger.Field("error", err.Error()), logger.Field("messageLog", messageLog), ) + return nil + } + + if err = l.svcCtx.LogModel.Insert(ctx, &log.SystemLog{ + Type: log.TypeEmailMessage.Uint8(), + Date: time.Now().Format("2006-01-02"), + ObjectID: 0, + Content: string(emailLog), + }); err != nil { + logger.WithContext(ctx).Error("[SendEmailLogic] Insert email log failed", + logger.Field("error", err.Error()), + logger.Field("emailLog", string(emailLog)), + ) + return nil } - logger.WithContext(ctx).Info("[SendEmailLogic] Send email", logger.Field("email", payload.Email), logger.Field("content", payload.Content)) return nil } diff --git a/queue/logic/order/activateOrderLogic.go b/queue/logic/order/activateOrderLogic.go index 19b164d..f6c8f9c 100644 --- a/queue/logic/order/activateOrderLogic.go +++ b/queue/logic/order/activateOrderLogic.go @@ -1,3 +1,5 @@ +// Package orderLogic provides order processing logic for handling various types of orders +// including subscription purchases, renewals, traffic resets, and balance recharges. package orderLogic import ( @@ -7,230 +9,325 @@ import ( "strconv" "time" - "github.com/perfect-panel/ppanel-server/pkg/constant" - - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/pkg/constant" + "github.com/perfect-panel/server/pkg/logger" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" "github.com/google/uuid" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/logic/telegram" - "github.com/perfect-panel/ppanel-server/internal/model/order" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/tool" - "github.com/perfect-panel/ppanel-server/pkg/uuidx" - "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/logic/telegram" + "github.com/perfect-panel/server/internal/model/order" + "github.com/perfect-panel/server/internal/model/subscribe" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/uuidx" + "github.com/perfect-panel/server/queue/types" "gorm.io/gorm" ) +// Order type constants define the different types of orders that can be processed const ( - Subscribe = 1 - Renewal = 2 - ResetTraffic = 3 - Recharge = 4 + OrderTypeSubscribe = 1 // New subscription purchase + OrderTypeRenewal = 2 // Subscription renewal + OrderTypeResetTraffic = 3 // Traffic quota reset + OrderTypeRecharge = 4 // Balance recharge ) +// Order status constants define the lifecycle states of an order +const ( + OrderStatusPending = 1 // Order created but not paid + OrderStatusPaid = 2 // Order paid and ready for processing + OrderStatusClose = 3 // Order closed/cancelled + OrderStatusFailed = 4 // Order processing failed + OrderStatusFinished = 5 // Order successfully completed +) + +// Predefined error variables for common error conditions +var ( + ErrInvalidOrderStatus = fmt.Errorf("invalid order status") + ErrInvalidOrderType = fmt.Errorf("invalid order type") +) + +// ActivateOrderLogic handles the activation and processing of paid orders type ActivateOrderLogic struct { - svc *svc.ServiceContext + svc *svc.ServiceContext // Service context containing dependencies } +// NewActivateOrderLogic creates a new instance of ActivateOrderLogic func NewActivateOrderLogic(svc *svc.ServiceContext) *ActivateOrderLogic { return &ActivateOrderLogic{ svc: svc, } } +// ProcessTask is the main entry point for processing order activation tasks. +// It handles the complete workflow of activating a paid order including validation, +// processing based on order type, and finalization. func (l *ActivateOrderLogic) ProcessTask(ctx context.Context, task *asynq.Task) error { - payload := types.ForthwithActivateOrderPayload{} - if err := json.Unmarshal(task.Payload(), &payload); err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Unmarshal payload failed", - logger.Field("error", err.Error()), - logger.Field("payload", string(task.Payload())), - ) - return nil - } - // Find order by order no - orderInfo, err := l.svc.OrderModel.FindOneByOrderNo(ctx, payload.OrderNo) + payload, err := l.parsePayload(ctx, task.Payload()) if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Find order failed", - logger.Field("error", err.Error()), - logger.Field("order_no", payload.OrderNo), - ) - return nil + return nil // Log and continue } - if orderInfo.Status != 2 { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Order status error", - logger.Field("order_no", orderInfo.OrderNo), - logger.Field("status", orderInfo.Status), - ) - return nil - } - switch orderInfo.Type { - case Subscribe: - err = l.NewPurchase(ctx, orderInfo) - case Renewal: - err = l.Renewal(ctx, orderInfo) - case ResetTraffic: - err = l.ResetTraffic(ctx, orderInfo) - case Recharge: - err = l.Recharge(ctx, orderInfo) - default: - logger.WithContext(ctx).Error("[ActivateOrderLogic] Order type is invalid", logger.Field("type", orderInfo.Type)) - } + orderInfo, err := l.validateAndGetOrder(ctx, payload.OrderNo) if err != nil { + return nil // Log and continue + } + + if err = l.processOrderByType(ctx, orderInfo); err != nil { logger.WithContext(ctx).Error("[ActivateOrderLogic] Process task failed", logger.Field("error", err.Error())) return nil } - // if coupon is not empty + + l.finalizeCouponAndOrder(ctx, orderInfo) + return nil +} + +// parsePayload unMarshals the task payload into a structured format +func (l *ActivateOrderLogic) parsePayload(ctx context.Context, payload []byte) (*types.ForthwithActivateOrderPayload, error) { + var p types.ForthwithActivateOrderPayload + if err := json.Unmarshal(payload, &p); err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Unmarshal payload failed", + logger.Field("error", err.Error()), + logger.Field("payload", string(payload)), + ) + return nil, err + } + return &p, nil +} + +// validateAndGetOrder retrieves an order by order number and validates its status +// Returns error if order is not found or not in paid status +func (l *ActivateOrderLogic) validateAndGetOrder(ctx context.Context, orderNo string) (*order.Order, error) { + orderInfo, err := l.svc.OrderModel.FindOneByOrderNo(ctx, orderNo) + if err != nil { + logger.WithContext(ctx).Error("Find order failed", + logger.Field("error", err.Error()), + logger.Field("order_no", orderNo), + ) + return nil, err + } + + if orderInfo.Status != OrderStatusPaid { + logger.WithContext(ctx).Error("Order status error", + logger.Field("order_no", orderInfo.OrderNo), + logger.Field("status", orderInfo.Status), + ) + return nil, ErrInvalidOrderStatus + } + + return orderInfo, nil +} + +// processOrderByType routes order processing based on the order type +func (l *ActivateOrderLogic) processOrderByType(ctx context.Context, orderInfo *order.Order) error { + switch orderInfo.Type { + case OrderTypeSubscribe: + return l.NewPurchase(ctx, orderInfo) + case OrderTypeRenewal: + return l.Renewal(ctx, orderInfo) + case OrderTypeResetTraffic: + return l.ResetTraffic(ctx, orderInfo) + case OrderTypeRecharge: + return l.Recharge(ctx, orderInfo) + default: + logger.WithContext(ctx).Error("Order type is invalid", logger.Field("type", orderInfo.Type)) + return ErrInvalidOrderType + } +} + +// finalizeCouponAndOrder handles post-processing tasks including coupon updates +// and order status finalization +func (l *ActivateOrderLogic) finalizeCouponAndOrder(ctx context.Context, orderInfo *order.Order) { + // Update coupon if exists if orderInfo.Coupon != "" { - // update coupon status - err = l.svc.CouponModel.UpdateCount(ctx, orderInfo.Coupon) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Update coupon status failed", + if err := l.svc.CouponModel.UpdateCount(ctx, orderInfo.Coupon); err != nil { + logger.WithContext(ctx).Error("Update coupon status failed", logger.Field("error", err.Error()), logger.Field("coupon", orderInfo.Coupon), ) } } - // update order status - orderInfo.Status = 5 - err = l.svc.OrderModel.Update(ctx, orderInfo) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Update order status failed", + + // Update order status + orderInfo.Status = OrderStatusFinished + if err := l.svc.OrderModel.Update(ctx, orderInfo); err != nil { + logger.WithContext(ctx).Error("Update order status failed", logger.Field("error", err.Error()), logger.Field("order_no", orderInfo.OrderNo), ) } +} +// NewPurchase handles new subscription purchase including user creation, +// subscription setup, commission processing, cache updates, and notifications +func (l *ActivateOrderLogic) NewPurchase(ctx context.Context, orderInfo *order.Order) error { + userInfo, err := l.getUserOrCreate(ctx, orderInfo) + if err != nil { + return err + } + + sub, err := l.getSubscribeInfo(ctx, orderInfo.SubscribeId) + if err != nil { + return err + } + + userSub, err := l.createUserSubscription(ctx, orderInfo, sub) + if err != nil { + return err + } + + // Handle commission in separate goroutine to avoid blocking + go l.handleCommission(context.Background(), userInfo, orderInfo) + + // Clear cache + l.clearServerCache(ctx, sub) + + // Send notifications + l.sendNotifications(ctx, orderInfo, userInfo, sub, userSub, telegram.PurchaseNotify) + + logger.WithContext(ctx).Info("Insert user subscribe success") return nil } -// NewPurchase New purchase -func (l *ActivateOrderLogic) NewPurchase(ctx context.Context, orderInfo *order.Order) error { - var userInfo *user.User - var err error +// getUserOrCreate retrieves an existing user or creates a new guest user based on order details +func (l *ActivateOrderLogic) getUserOrCreate(ctx context.Context, orderInfo *order.Order) (*user.User, error) { if orderInfo.UserId != 0 { - // find user by user id - userInfo, err = l.svc.UserModel.FindOne(ctx, orderInfo.UserId) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user failed", - logger.Field("error", err.Error()), - logger.Field("user_id", orderInfo.UserId), - logger.Field("user_id", orderInfo.UserId), - ) - return err - } - } else { - // If User ID is 0, it means that the order is a guest order, need to create a new user - // query info with redis - cacheKey := fmt.Sprintf(constant.TempOrderCacheKey, orderInfo.OrderNo) - data, err := l.svc.Redis.Get(ctx, cacheKey).Result() - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Get temp order cache failed", - logger.Field("error", err.Error()), - logger.Field("cache_key", cacheKey), - ) - return err - } - var tempOrder constant.TemporaryOrderInfo - if err = json.Unmarshal([]byte(data), &tempOrder); err != nil { - logger.WithContext(ctx).Errorw("[ActivateOrderLogic] Unmarshal temp order failed", - logger.Field("error", err.Error()), - ) - return err - } - // create user - - userInfo = &user.User{ - Password: tool.EncodePassWord(tempOrder.Password), - AuthMethods: []user.AuthMethods{ - { - AuthType: tempOrder.AuthType, - AuthIdentifier: tempOrder.Identifier, - }, - }, - } - err = l.svc.UserModel.Transaction(ctx, func(tx *gorm.DB) error { - // Save user information - if err := tx.Save(userInfo).Error; err != nil { - return err - } - // Generate ReferCode - userInfo.ReferCode = uuidx.UserInviteCode(userInfo.Id) - // Update ReferCode - if err := tx.Model(&user.User{}).Where("id = ?", userInfo.Id).Update("refer_code", userInfo.ReferCode).Error; err != nil { - return err - } - orderInfo.UserId = userInfo.Id - return tx.Model(&order.Order{}).Where("order_no = ?", orderInfo.OrderNo).Update("user_id", userInfo.Id).Error - }) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Create user failed", - logger.Field("error", err.Error()), - ) - return err - } - logger.WithContext(ctx).Info("[ActivateOrderLogic] Create guest user success", logger.Field("user_id", userInfo.Id), logger.Field("Identifier", tempOrder.Identifier), logger.Field("AuthType", tempOrder.AuthType)) + return l.getExistingUser(ctx, orderInfo.UserId) } - // find subscribe by id - sub, err := l.svc.SubscribeModel.FindOne(ctx, orderInfo.SubscribeId) + return l.createGuestUser(ctx, orderInfo) +} + +// getExistingUser retrieves user information by user ID +func (l *ActivateOrderLogic) getExistingUser(ctx context.Context, userId int64) (*user.User, error) { + userInfo, err := l.svc.UserModel.FindOne(ctx, userId) if err != nil { - logger.WithContext(ctx).Errorw("[ActivateOrderLogic] Find subscribe failed", + logger.WithContext(ctx).Error("Find user failed", logger.Field("error", err.Error()), - logger.Field("subscribe_id", orderInfo.SubscribeId), + logger.Field("user_id", userId), ) - return err + return nil, err } - // create user subscribe - now := time.Now() + return userInfo, nil +} - //系统开启了试用订阅功能,并且当前存在试用订阅 - if l.svc.Config.Register.EnableTrial && l.svc.Config.Register.TrialSubscribe != 0 { - //查询使用订阅套餐 - subscribeDetails, subErr := l.svc.UserModel.QueryUserSubscribe(ctx, userInfo.Id, 1, 2) - if subErr != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] disable user try out subscribe failed", - logger.Field("error", subErr.Error()), - ) - } else { - for _, item := range subscribeDetails { - if item.Subscribe.Id == l.svc.Config.Register.TrialSubscribe { - err = l.svc.UserModel.UpdateSubscribe(ctx, &user.Subscribe{ - Id: item.Id, - UserId: item.UserId, - OrderId: item.OrderId, - SubscribeId: item.SubscribeId, - StartTime: item.StartTime, - ExpireTime: now, - Traffic: item.Traffic, - Download: item.Download, - Upload: item.Upload, - Token: item.Token, - UUID: item.UUID, - FinishedAt: now, - Status: 3, - }) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] disable user try out subscribe failed", - logger.Field("error", err.Error()), - ) - } else { - logger.WithContext(ctx).Info("[ActivateOrderLogic] disable user try out subscribe success", - logger.Field("user_id", userInfo.Id), - logger.Field("subscribe_id", item.SubscribeId), - ) - } - break - } - } +// createGuestUser creates a new user account for guest orders using temporary order information +// stored in Redis cache +func (l *ActivateOrderLogic) createGuestUser(ctx context.Context, orderInfo *order.Order) (*user.User, error) { + tempOrder, err := l.getTempOrderInfo(ctx, orderInfo.OrderNo) + if err != nil { + return nil, err + } + + userInfo := &user.User{ + Password: tool.EncodePassWord(tempOrder.Password), + AuthMethods: []user.AuthMethods{ + { + AuthType: tempOrder.AuthType, + AuthIdentifier: tempOrder.Identifier, + }, + }, + } + + err = l.svc.UserModel.Transaction(ctx, func(tx *gorm.DB) error { + if err := tx.Save(userInfo).Error; err != nil { + return err } + + userInfo.ReferCode = uuidx.UserInviteCode(userInfo.Id) + if err := tx.Model(&user.User{}).Where("id = ?", userInfo.Id).Update("refer_code", userInfo.ReferCode).Error; err != nil { + return err + } + + orderInfo.UserId = userInfo.Id + return tx.Model(&order.Order{}).Where("order_no = ?", orderInfo.OrderNo).Update("user_id", userInfo.Id).Error + }) + + if err != nil { + logger.WithContext(ctx).Error("Create user failed", logger.Field("error", err.Error())) + return nil, err } - userSub := user.Subscribe{ - Id: 0, + // Handle referrer relationship + l.handleReferrer(ctx, userInfo, tempOrder.InviteCode) + + logger.WithContext(ctx).Info("Create guest user success", + logger.Field("user_id", userInfo.Id), + logger.Field("identifier", tempOrder.Identifier), + logger.Field("auth_type", tempOrder.AuthType), + ) + + return userInfo, nil +} + +// getTempOrderInfo retrieves temporary order information from Redis cache +func (l *ActivateOrderLogic) getTempOrderInfo(ctx context.Context, orderNo string) (*constant.TemporaryOrderInfo, error) { + cacheKey := fmt.Sprintf(constant.TempOrderCacheKey, orderNo) + data, err := l.svc.Redis.Get(ctx, cacheKey).Result() + if err != nil { + logger.WithContext(ctx).Error("Get temp order cache failed", + logger.Field("error", err.Error()), + logger.Field("cache_key", cacheKey), + ) + return nil, err + } + + var tempOrder constant.TemporaryOrderInfo + if err = tempOrder.Unmarshal([]byte(data)); err != nil { + logger.WithContext(ctx).Error("Unmarshal temp order cache failed", + logger.Field("error", err.Error()), + logger.Field("cache_key", cacheKey), + logger.Field("data", data), + ) + return nil, err + } + + return &tempOrder, nil +} + +// handleReferrer establishes referrer relationship if an invite code is provided +func (l *ActivateOrderLogic) handleReferrer(ctx context.Context, userInfo *user.User, inviteCode string) { + if inviteCode == "" { + return + } + + referer, err := l.svc.UserModel.FindOneByReferCode(ctx, inviteCode) + if err != nil { + logger.WithContext(ctx).Error("Find referer failed", + logger.Field("error", err.Error()), + logger.Field("refer_code", inviteCode), + ) + return + } + + userInfo.RefererId = referer.Id + if err = l.svc.UserModel.Update(ctx, userInfo); err != nil { + logger.WithContext(ctx).Error("Update user referer failed", + logger.Field("error", err.Error()), + logger.Field("user_id", userInfo.Id), + ) + } +} + +// getSubscribeInfo retrieves subscription plan details by subscription ID +func (l *ActivateOrderLogic) getSubscribeInfo(ctx context.Context, subscribeId int64) (*subscribe.Subscribe, error) { + sub, err := l.svc.SubscribeModel.FindOne(ctx, subscribeId) + if err != nil { + logger.WithContext(ctx).Error("Find subscribe failed", + logger.Field("error", err.Error()), + logger.Field("subscribe_id", subscribeId), + ) + return nil, err + } + return sub, nil +} + +// createUserSubscription creates a new user subscription record based on order and subscription plan details +func (l *ActivateOrderLogic) createUserSubscription(ctx context.Context, orderInfo *order.Order, sub *subscribe.Subscribe) (*user.Subscribe, error) { + now := time.Now() + userSub := &user.Subscribe{ UserId: orderInfo.UserId, OrderId: orderInfo.Id, SubscribeId: orderInfo.SubscribeId, @@ -243,383 +340,373 @@ func (l *ActivateOrderLogic) NewPurchase(ctx context.Context, orderInfo *order.O UUID: uuid.New().String(), Status: 1, } - err = l.svc.UserModel.InsertSubscribe(ctx, &userSub) + + if err := l.svc.UserModel.InsertSubscribe(ctx, userSub); err != nil { + logger.WithContext(ctx).Error("Insert user subscribe failed", logger.Field("error", err.Error())) + return nil, err + } + + return userSub, nil +} + +// handleCommission processes referral commission for the referrer if applicable. +// This runs asynchronously to avoid blocking the main order processing flow. +func (l *ActivateOrderLogic) handleCommission(ctx context.Context, userInfo *user.User, orderInfo *order.Order) { + if !l.shouldProcessCommission(userInfo, orderInfo.IsNew) { + return + } + + referer, err := l.svc.UserModel.FindOne(ctx, userInfo.RefererId) + if err != nil { + logger.WithContext(ctx).Error("Find referer failed", + logger.Field("error", err.Error()), + logger.Field("referer_id", userInfo.RefererId), + ) + return + } + + var referralPercentage uint8 + if referer.ReferralPercentage != 0 { + referralPercentage = referer.ReferralPercentage + } else { + referralPercentage = uint8(l.svc.Config.Invite.ReferralPercentage) + } + + // Order commission calculation: (Order Amount - Order Fee) * Referral Percentage + amount := l.calculateCommission(orderInfo.Amount-orderInfo.FeeAmount, referralPercentage) + + // Use transaction for commission updates + err = l.svc.DB.Transaction(func(tx *gorm.DB) error { + referer.Commission += amount + if err = l.svc.UserModel.Update(ctx, referer, tx); err != nil { + return err + } + + var commissionType uint16 + switch orderInfo.Type { + case OrderTypeSubscribe: + commissionType = log.CommissionTypePurchase + case OrderTypeRenewal: + commissionType = log.CommissionTypeRenewal + } + + commissionLog := &log.Commission{ + Type: commissionType, + Amount: amount, + OrderNo: orderInfo.OrderNo, + Timestamp: orderInfo.CreatedAt.UnixMilli(), + } + + content, _ := commissionLog.Marshal() + return tx.Model(&log.SystemLog{}).Create(&log.SystemLog{ + Type: log.TypeCommission.Uint8(), + Date: time.Now().Format("2006-01-02"), + ObjectID: referer.Id, + Content: string(content), + }).Error + }) if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Insert user subscribe failed", + logger.WithContext(ctx).Error("Update referer commission failed", logger.Field("error", err.Error())) + return + } + + // Update cache + if err = l.svc.UserModel.UpdateUserCache(ctx, referer); err != nil { + logger.WithContext(ctx).Error("Update referer cache failed", logger.Field("error", err.Error()), + logger.Field("user_id", referer.Id), ) + } +} + +// shouldProcessCommission determines if commission should be processed based on +// referrer existence, commission settings, and order type +func (l *ActivateOrderLogic) shouldProcessCommission(userInfo *user.User, isFirstPurchase bool) bool { + if userInfo == nil || userInfo.RefererId == 0 { + return false + } + + referer, err := l.svc.UserModel.FindOne(context.Background(), userInfo.RefererId) + if err != nil { + logger.Errorw("Find referer failed", + logger.Field("error", err.Error()), + logger.Field("referer_id", userInfo.RefererId)) + return false + } + if referer == nil { + return false + } + + // use referer's custom settings if set + if referer.ReferralPercentage > 0 { + if referer.OnlyFirstPurchase != nil && *referer.OnlyFirstPurchase && !isFirstPurchase { + return false + } + return true + } + + // use global settings + if l.svc.Config.Invite.ReferralPercentage == 0 { + return false + } + if l.svc.Config.Invite.OnlyFirstPurchase && !isFirstPurchase { + return false + } + + return true +} + +// calculateCommission computes the commission amount based on order price and referral percentage +func (l *ActivateOrderLogic) calculateCommission(price int64, percentage uint8) int64 { + return int64(float64(price) * (float64(percentage) / 100)) +} + +// clearServerCache clears user list cache for all servers associated with the subscription +func (l *ActivateOrderLogic) clearServerCache(ctx context.Context, sub *subscribe.Subscribe) { + if err := l.svc.SubscribeModel.ClearCache(ctx, sub.Id); err != nil { + logger.WithContext(ctx).Error("[Order Queue] Clear subscribe cache failed", logger.Field("error", err.Error())) + } +} + +// Renewal handles subscription renewal including subscription extension, +// traffic reset (if configured), commission processing, and notifications +func (l *ActivateOrderLogic) Renewal(ctx context.Context, orderInfo *order.Order) error { + userInfo, err := l.getExistingUser(ctx, orderInfo.UserId) + if err != nil { return err } - // handler commission - if userInfo.RefererId != 0 && - l.svc.Config.Invite.ReferralPercentage != 0 && - (!l.svc.Config.Invite.OnlyFirstPurchase || orderInfo.IsNew) { - referer, err := l.svc.UserModel.FindOne(ctx, userInfo.RefererId) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Find referer failed", - logger.Field("error", err.Error()), - logger.Field("referer_id", userInfo.RefererId), - ) - goto updateCache - } - // calculate commission - amount := float64(orderInfo.Price) * (float64(l.svc.Config.Invite.ReferralPercentage) / 100) - referer.Commission += int64(amount) - err = l.svc.UserModel.Update(ctx, referer) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Update referer commission failed", - logger.Field("error", err.Error()), - ) - goto updateCache - } - // create commission log - commissionLog := user.CommissionLog{ - UserId: referer.Id, - OrderNo: orderInfo.OrderNo, - Amount: int64(amount), - } - err = l.svc.UserModel.InsertCommissionLog(ctx, &commissionLog) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Insert commission log failed", - logger.Field("error", err.Error()), - ) - } - err = l.svc.UserModel.UpdateUserCache(ctx, referer) - if err != nil { - logger.WithContext(ctx).Errorw("[ActivateOrderLogic] Update referer cache", logger.Field("error", err.Error()), logger.Field("user_id", referer.Id)) - } - } -updateCache: - for _, id := range tool.StringToInt64Slice(sub.Server) { - cacheKey := fmt.Sprintf("%s%d", config.ServerUserListCacheKey, id) - err = l.svc.Redis.Del(ctx, cacheKey).Err() - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Del server user list cache failed", - logger.Field("error", err.Error()), - logger.Field("cache_key", cacheKey), - ) - } - } - data, err := l.svc.ServerModel.FindServerListByGroupIds(ctx, tool.StringToInt64Slice(sub.ServerGroup)) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Find server list failed", logger.Field("error", err.Error())) - return nil - } - for _, item := range data { - cacheKey := fmt.Sprintf("%s%d", config.ServerUserListCacheKey, item.Id) - err = l.svc.Redis.Del(ctx, cacheKey).Err() - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Del server user list cache failed", - logger.Field("error", err.Error()), - logger.Field("cache_key", cacheKey), - ) - } - } - userTelegramChatId, ok := findTelegram(userInfo) - // sendMessage To Telegram - if ok { - text, err := tool.RenderTemplateToString(telegram.PurchaseNotify, map[string]string{ - "OrderNo": orderInfo.OrderNo, - "SubscribeName": sub.Name, - "OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100), - "ExpireTime": userSub.ExpireTime.Format("2006-01-02 15:04:05"), - }) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Render template failed", - logger.Field("error", err.Error()), - ) - } - l.sendUserNotifyWithTelegram(userTelegramChatId, text) - } - // send message to admin - text, err := tool.RenderTemplateToString(telegram.AdminOrderNotify, map[string]string{ - "OrderNo": orderInfo.OrderNo, - "TradeNo": orderInfo.TradeNo, - "SubscribeName": sub.Name, - //"UserEmail": userInfo.Email, - "OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100), - "OrderStatus": "已支付", - "OrderTime": orderInfo.CreatedAt.Format("2006-01-02 15:04:05"), - "PaymentMethod": orderInfo.Method, - }) + userSub, err := l.getUserSubscription(ctx, orderInfo.SubscribeToken) if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Render AdminOrderNotify template failed", + return err + } + + sub, err := l.getSubscribeInfo(ctx, orderInfo.SubscribeId) + if err != nil { + return err + } + + if err = l.updateSubscriptionForRenewal(ctx, userSub, sub, orderInfo); err != nil { + return err + } + + // Clear user subscription cache + err = l.svc.UserModel.ClearSubscribeCache(ctx, userSub) + if err != nil { + logger.WithContext(ctx).Error("Clear user subscribe cache failed", logger.Field("error", err.Error()), + logger.Field("subscribe_id", userSub.Id), + logger.Field("user_id", userInfo.Id), ) } - l.sendAdminNotifyWithTelegram(ctx, text) - logger.WithContext(ctx).Info("[ActivateOrderLogic] Insert user subscribe success") + + // Clear cache + l.clearServerCache(ctx, sub) + + // Handle commission + go l.handleCommission(context.Background(), userInfo, orderInfo) + + // Send notifications + l.sendNotifications(ctx, orderInfo, userInfo, sub, userSub, telegram.RenewalNotify) + return nil } -// Renewal Renewal -func (l *ActivateOrderLogic) Renewal(ctx context.Context, orderInfo *order.Order) error { - // find user by user id - userInfo, err := l.svc.UserModel.FindOne(ctx, orderInfo.UserId) +// getUserSubscription retrieves user subscription by token +func (l *ActivateOrderLogic) getUserSubscription(ctx context.Context, token string) (*user.Subscribe, error) { + userSub, err := l.svc.UserModel.FindOneSubscribeByToken(ctx, token) if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user failed", - logger.Field("error", err.Error()), - logger.Field("user_id", orderInfo.UserId), - ) - return err - } - // find user subscribe by subscribe token - userSub, err := l.svc.UserModel.FindOneSubscribeByOrderId(ctx, orderInfo.ParentId) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user subscribe failed", - logger.Field("error", err.Error()), - logger.Field("order_id", orderInfo.Id), - ) - return err - } - // find subscribe by id - sub, err := l.svc.SubscribeModel.FindOne(ctx, orderInfo.SubscribeId) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Find subscribe failed", - logger.Field("error", err.Error()), - logger.Field("subscribe_id", orderInfo.SubscribeId), - logger.Field("order_id", orderInfo.Id), - ) - return err + logger.WithContext(ctx).Error("Find user subscribe failed", logger.Field("error", err.Error())) + return nil, err } + return userSub, nil +} + +// updateSubscriptionForRenewal updates subscription details for renewal including +// expiration time extension and traffic reset if configured +func (l *ActivateOrderLogic) updateSubscriptionForRenewal(ctx context.Context, userSub *user.Subscribe, sub *subscribe.Subscribe, orderInfo *order.Order) error { now := time.Now() if userSub.ExpireTime.Before(now) { userSub.ExpireTime = now - userSub.Status = 1 + } + today := time.Now().Day() + resetDay := userSub.ExpireTime.Day() + + // Reset traffic if enabled + if (sub.RenewalReset != nil && *sub.RenewalReset) || today == resetDay { + userSub.Download = 0 + userSub.Upload = 0 } - //fix bug:FinishedAt causes the update subscription to fail - if now.AddDate(-30, 0, 0).After(userSub.FinishedAt) { - userSub.FinishedAt = now + if userSub.FinishedAt != nil { + if userSub.FinishedAt.Before(now) && today > resetDay { + // reset user traffic if finished at is before now + userSub.Download = 0 + userSub.Upload = 0 + } + + userSub.FinishedAt = nil } userSub.ExpireTime = tool.AddTime(sub.UnitTime, orderInfo.Quantity, userSub.ExpireTime) - // update user subscribe - err = l.svc.UserModel.UpdateSubscribe(ctx, userSub) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Update user subscribe failed", - logger.Field("error", err.Error()), - ) + userSub.Status = 1 + + if err := l.svc.UserModel.UpdateSubscribe(ctx, userSub); err != nil { + logger.WithContext(ctx).Error("Update user subscribe failed", logger.Field("error", err.Error())) return err } - // handler commission - if userInfo.RefererId != 0 && - l.svc.Config.Invite.ReferralPercentage != 0 && - !l.svc.Config.Invite.OnlyFirstPurchase { - referer, err := l.svc.UserModel.FindOne(ctx, userInfo.RefererId) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Find referer failed", - logger.Field("error", err.Error()), - logger.Field("referer_id", userInfo.RefererId), - ) - goto sendMessage - } - // calculate commission - amount := float64(orderInfo.Price) * (float64(l.svc.Config.Invite.ReferralPercentage) / 100) - referer.Commission += int64(amount) - err = l.svc.UserModel.Update(ctx, referer) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Update referer commission failed", - logger.Field("error", err.Error()), - ) - goto sendMessage - } - // create commission log - commissionLog := user.CommissionLog{ - UserId: referer.Id, - OrderNo: orderInfo.OrderNo, - Amount: int64(amount), - } - err = l.svc.UserModel.InsertCommissionLog(ctx, &commissionLog) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Insert commission log failed", - logger.Field("error", err.Error()), - ) - } - err = l.svc.UserModel.UpdateUserCache(ctx, referer) - if err != nil { - logger.WithContext(ctx).Errorw("[ActivateOrderLogic] Update referer cache", logger.Field("error", err.Error()), logger.Field("user_id", referer.Id)) - } - } -sendMessage: - userTelegramChatId, ok := findTelegram(userInfo) - // SendMessage To Telegram - if ok { - text, err := tool.RenderTemplateToString(telegram.RenewalNotify, map[string]string{ - "OrderNo": orderInfo.OrderNo, - "SubscribeName": sub.Name, - "OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100), - "ExpireTime": userSub.ExpireTime.Format("2006-01-02 15:04:05"), - }) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Render template failed", - logger.Field("error", err.Error()), - ) - } - l.sendUserNotifyWithTelegram(userTelegramChatId, text) - } - // send message to admin - text, err := tool.RenderTemplateToString(telegram.AdminOrderNotify, map[string]string{ - "OrderNo": orderInfo.OrderNo, - "TradeNo": orderInfo.TradeNo, - "SubscribeName": sub.Name, - //"UserEmail": userInfo.Email, - "OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100), - "OrderStatus": "已支付", - "OrderTime": orderInfo.CreatedAt.Format("2006-01-02 15:04:05"), - "PaymentMethod": orderInfo.Method, - }) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Render AdminOrderNotify template failed", - logger.Field("error", err.Error()), - ) - } - l.sendAdminNotifyWithTelegram(ctx, text) return nil } -// ResetTraffic Reset traffic +// ResetTraffic handles traffic quota reset for existing subscriptions func (l *ActivateOrderLogic) ResetTraffic(ctx context.Context, orderInfo *order.Order) error { - // find user by user id - userInfo, err := l.svc.UserModel.FindOne(ctx, orderInfo.UserId) + userInfo, err := l.getExistingUser(ctx, orderInfo.UserId) if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user failed", - logger.Field("error", err.Error()), - logger.Field("user_id", orderInfo.UserId), - ) return err } - // Generate a Subscribe Token through orderNo - // find user subscribe by subscribe token - userSub, err := l.svc.UserModel.FindOneSubscribeByToken(ctx, orderInfo.SubscribeToken) + + userSub, err := l.getUserSubscription(ctx, orderInfo.SubscribeToken) if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user subscribe failed", - logger.Field("error", err.Error()), - logger.Field("order_id", orderInfo.Id), - ) return err } + + // Reset traffic userSub.Download = 0 userSub.Upload = 0 userSub.Status = 1 - // update user subscribe - err = l.svc.UserModel.UpdateSubscribe(ctx, userSub) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Update user subscribe failed", - logger.Field("error", err.Error()), - ) + + if err := l.svc.UserModel.UpdateSubscribe(ctx, userSub); err != nil { + logger.WithContext(ctx).Error("Update user subscribe failed", logger.Field("error", err.Error())) return err } - sub, err := l.svc.SubscribeModel.FindOne(ctx, userSub.SubscribeId) + + sub, err := l.getSubscribeInfo(ctx, userSub.SubscribeId) if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Find subscribe failed", - logger.Field("error", err.Error()), - logger.Field("subscribe_id", userSub.SubscribeId), - ) - return nil - } - userTelegramChatId, ok := findTelegram(userInfo) - // SendMessage To Telegram - if ok { - text, err := tool.RenderTemplateToString(telegram.ResetTrafficNotify, map[string]string{ - "OrderNo": orderInfo.OrderNo, - "SubscribeName": sub.Name, - "ResetTime": time.Now().Format("2006-01-02 15:04:05"), - "ExpireTime": userSub.ExpireTime.Format("2006-01-02 15:04:05"), - }) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Render template failed", - logger.Field("error", err.Error()), - ) - } - l.sendUserNotifyWithTelegram(userTelegramChatId, text) + return err } - // send message to admin - text, err := tool.RenderTemplateToString(telegram.AdminOrderNotify, map[string]string{ - "OrderNo": orderInfo.OrderNo, - "TradeNo": orderInfo.TradeNo, - "SubscribeName": "流量重置", - "OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100), - "OrderStatus": "已支付", - "OrderTime": orderInfo.CreatedAt.Format("2006-01-02 15:04:05"), - "PaymentMethod": orderInfo.Method, - }) + // Clear user subscription cache + err = l.svc.UserModel.ClearSubscribeCache(ctx, userSub) if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Render AdminOrderNotify template failed", + logger.WithContext(ctx).Error("Clear user subscribe cache failed", logger.Field("error", err.Error()), + logger.Field("subscribe_id", userSub.Id), + logger.Field("user_id", userInfo.Id), ) } - l.sendAdminNotifyWithTelegram(ctx, text) + + // Clear cache + l.clearServerCache(ctx, sub) + + // insert reset traffic log + resetLog := &log.ResetSubscribe{ + Type: log.ResetSubscribeTypePaid, + UserId: userInfo.Id, + OrderNo: orderInfo.OrderNo, + Timestamp: time.Now().UnixMilli(), + } + + content, _ := resetLog.Marshal() + if err = l.svc.LogModel.Insert(ctx, &log.SystemLog{ + Type: log.TypeResetSubscribe.Uint8(), + Date: time.Now().Format(time.DateOnly), + ObjectID: userSub.Id, + Content: string(content), + }); err != nil { + logger.WithContext(ctx).Error("[Order Queue]Insert reset subscribe log failed", logger.Field("error", err.Error())) + } + + // Send notifications + l.sendNotifications(ctx, orderInfo, userInfo, sub, userSub, telegram.ResetTrafficNotify) + return nil } -// Recharge Recharge to user +// Recharge handles balance recharge orders including balance updates, +// transaction logging, and notifications func (l *ActivateOrderLogic) Recharge(ctx context.Context, orderInfo *order.Order) error { - // find user by user id - userInfo, err := l.svc.UserModel.FindOne(ctx, orderInfo.UserId) + userInfo, err := l.getExistingUser(ctx, orderInfo.UserId) if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user failed", - logger.Field("error", err.Error()), - logger.Field("user_id", orderInfo.UserId), - ) return err } - userInfo.Balance += orderInfo.Price - // update user + + // Update balance in transaction err = l.svc.DB.Transaction(func(tx *gorm.DB) error { - err = l.svc.UserModel.Update(ctx, userInfo, tx) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Update user failed", - logger.Field("error", err.Error()), - ) - return err - } - // Create Balance Log - balanceLog := user.BalanceLog{ - UserId: orderInfo.UserId, - Amount: orderInfo.Price, - Type: 1, - OrderId: orderInfo.Id, - Balance: userInfo.Balance, - } - err = l.svc.UserModel.InsertBalanceLog(ctx, &balanceLog, tx) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Insert balance log failed", - logger.Field("error", err.Error()), - ) + userInfo.Balance += orderInfo.Price + if err = l.svc.UserModel.Update(ctx, userInfo, tx); err != nil { return err } - return nil + balanceLog := &log.Balance{ + Amount: orderInfo.Price, + Type: log.BalanceTypeRecharge, + OrderNo: orderInfo.OrderNo, + Balance: userInfo.Balance, + Timestamp: time.Now().UnixMilli(), + } + content, _ := balanceLog.Marshal() + + return tx.Model(&log.SystemLog{}).Create(&log.SystemLog{ + Type: log.TypeBalance.Uint8(), + Date: time.Now().Format("2006-01-02"), + ObjectID: userInfo.Id, + Content: string(content), + }).Error }) + if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Database transaction failed", - logger.Field("error", err.Error()), - ) + logger.WithContext(ctx).Error("[Recharge] Database transaction failed", logger.Field("error", err.Error())) return err } - userTelegramChatId, ok := findTelegram(userInfo) - // SendMessage To Telegram - if ok { - text, err := tool.RenderTemplateToString(telegram.RechargeNotify, map[string]string{ + + // clear user cache + if err = l.svc.UserModel.UpdateUserCache(ctx, userInfo); err != nil { + logger.WithContext(ctx).Error("[Recharge] Update user cache failed", logger.Field("error", err.Error())) + return err + } + + // Send notifications + l.sendRechargeNotifications(ctx, orderInfo, userInfo) + + return nil +} + +// sendNotifications sends both user and admin notifications for order completion +func (l *ActivateOrderLogic) sendNotifications(ctx context.Context, orderInfo *order.Order, userInfo *user.User, sub *subscribe.Subscribe, userSub *user.Subscribe, notifyType string) { + // Send user notification + if telegramId, ok := findTelegram(userInfo); ok { + templateData := l.buildUserNotificationData(orderInfo, sub, userSub) + if text, err := tool.RenderTemplateToString(notifyType, templateData); err == nil { + l.sendUserNotifyWithTelegram(telegramId, text) + } + } + + // Send admin notification + adminData := l.buildAdminNotificationData(orderInfo, sub) + if text, err := tool.RenderTemplateToString(telegram.AdminOrderNotify, adminData); err == nil { + l.sendAdminNotifyWithTelegram(ctx, text) + } +} + +// sendRechargeNotifications sends specific notifications for balance recharge orders +func (l *ActivateOrderLogic) sendRechargeNotifications(ctx context.Context, orderInfo *order.Order, userInfo *user.User) { + // Send user notification + if telegramId, ok := findTelegram(userInfo); ok { + templateData := map[string]string{ "OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100), "PaymentMethod": orderInfo.Method, "Time": orderInfo.CreatedAt.Format("2006-01-02 15:04:05"), "Balance": fmt.Sprintf("%.2f", float64(userInfo.Balance)/100), - }) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Render template failed", - logger.Field("error", err.Error()), - ) } - l.sendUserNotifyWithTelegram(userTelegramChatId, text) + if text, err := tool.RenderTemplateToString(telegram.RechargeNotify, templateData); err == nil { + l.sendUserNotifyWithTelegram(telegramId, text) + } } - // send message to admin - text, err := tool.RenderTemplateToString(telegram.AdminOrderNotify, map[string]string{ + + // Send admin notification + adminData := map[string]string{ "OrderNo": orderInfo.OrderNo, "TradeNo": orderInfo.TradeNo, "OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100), @@ -627,65 +714,83 @@ func (l *ActivateOrderLogic) Recharge(ctx context.Context, orderInfo *order.Orde "OrderStatus": "已支付", "OrderTime": orderInfo.CreatedAt.Format("2006-01-02 15:04:05"), "PaymentMethod": orderInfo.Method, - }) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Render AdminOrderNotify template failed", - logger.Field("error", err.Error()), - ) } - l.sendAdminNotifyWithTelegram(ctx, text) - return nil + if text, err := tool.RenderTemplateToString(telegram.AdminOrderNotify, adminData); err == nil { + l.sendAdminNotifyWithTelegram(ctx, text) + } } -// sendUserNotifyWithTelegram send message to user +// buildUserNotificationData creates template data for user notifications +func (l *ActivateOrderLogic) buildUserNotificationData(orderInfo *order.Order, sub *subscribe.Subscribe, userSub *user.Subscribe) map[string]string { + data := map[string]string{ + "OrderNo": orderInfo.OrderNo, + "SubscribeName": sub.Name, + "OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100), + } + + if userSub != nil { + data["ExpireTime"] = userSub.ExpireTime.Format("2006-01-02 15:04:05") + data["ResetTime"] = time.Now().Format("2006-01-02 15:04:05") + } + + return data +} + +// buildAdminNotificationData creates template data for admin notifications +func (l *ActivateOrderLogic) buildAdminNotificationData(orderInfo *order.Order, sub *subscribe.Subscribe) map[string]string { + subscribeName := sub.Name + if orderInfo.Type == OrderTypeResetTraffic { + subscribeName = "流量重置" + } + + return map[string]string{ + "OrderNo": orderInfo.OrderNo, + "TradeNo": orderInfo.TradeNo, + "SubscribeName": subscribeName, + "OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100), + "OrderStatus": "已支付", + "OrderTime": orderInfo.CreatedAt.Format("2006-01-02 15:04:05"), + "PaymentMethod": orderInfo.Method, + } +} + +// sendUserNotifyWithTelegram sends a notification message to a user via Telegram func (l *ActivateOrderLogic) sendUserNotifyWithTelegram(chatId int64, text string) { msg := tgbotapi.NewMessage(chatId, text) msg.ParseMode = "markdown" - _, err := l.svc.TelegramBot.Send(msg) - if err != nil { - logger.Error("[ActivateOrderLogic] Send telegram user message failed", - logger.Field("error", err.Error()), - ) + if _, err := l.svc.TelegramBot.Send(msg); err != nil { + logger.Error("Send telegram user message failed", logger.Field("error", err.Error())) } } -// sendAdminNotifyWithTelegram send message to admin +// sendAdminNotifyWithTelegram sends a notification message to all admin users via Telegram func (l *ActivateOrderLogic) sendAdminNotifyWithTelegram(ctx context.Context, text string) { admins, err := l.svc.UserModel.QueryAdminUsers(ctx) if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Query admin users failed", - logger.Field("error", err.Error()), - ) + logger.WithContext(ctx).Error("Query admin users failed", logger.Field("error", err.Error())) return } + for _, admin := range admins { - telegramId, ok := findTelegram(admin) - if !ok { - continue - } - msg := tgbotapi.NewMessage(telegramId, text) - msg.ParseMode = "markdown" - _, err := l.svc.TelegramBot.Send(msg) - if err != nil { - logger.WithContext(ctx).Error("[ActivateOrderLogic] Send telegram admin message failed", - logger.Field("error", err.Error()), - ) + if telegramId, ok := findTelegram(admin); ok { + msg := tgbotapi.NewMessage(telegramId, text) + msg.ParseMode = "markdown" + if _, err := l.svc.TelegramBot.Send(msg); err != nil { + logger.WithContext(ctx).Error("Send telegram admin message failed", logger.Field("error", err.Error())) + } } } } -// findTelegram find user telegram id +// findTelegram extracts Telegram chat ID from user authentication methods. +// Returns the chat ID and a boolean indicating if Telegram auth was found. func findTelegram(u *user.User) (int64, bool) { for _, item := range u.AuthMethods { if item.AuthType == "telegram" { - // string to int64 - parseInt, err := strconv.ParseInt(item.AuthIdentifier, 10, 64) - if err != nil { - return 0, false + if telegramId, err := strconv.ParseInt(item.AuthIdentifier, 10, 64); err == nil { + return telegramId, true } - return parseInt, true } - } return 0, false } diff --git a/queue/logic/order/activateOrderLogic.go_bak b/queue/logic/order/activateOrderLogic.go_bak new file mode 100644 index 0000000..f57f57f --- /dev/null +++ b/queue/logic/order/activateOrderLogic.go_bak @@ -0,0 +1,675 @@ +package orderLogic + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "time" + + "github.com/perfect-panel/server/pkg/constant" + + "github.com/perfect-panel/server/pkg/logger" + + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" + "github.com/google/uuid" + "github.com/hibiken/asynq" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/logic/telegram" + "github.com/perfect-panel/server/internal/model/order" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/uuidx" + "github.com/perfect-panel/server/queue/types" + "gorm.io/gorm" +) + +const ( + Subscribe = 1 + Renewal = 2 + ResetTraffic = 3 + Recharge = 4 +) + +type ActivateOrderLogic struct { + svc *svc.ServiceContext +} + +func NewActivateOrderLogic(svc *svc.ServiceContext) *ActivateOrderLogic { + return &ActivateOrderLogic{ + svc: svc, + } +} + +func (l *ActivateOrderLogic) ProcessTask(ctx context.Context, task *asynq.Task) error { + payload := types.ForthwithActivateOrderPayload{} + if err := json.Unmarshal(task.Payload(), &payload); err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Unmarshal payload failed", + logger.Field("error", err.Error()), + logger.Field("payload", string(task.Payload())), + ) + return nil + } + // Find order by order no + orderInfo, err := l.svc.OrderModel.FindOneByOrderNo(ctx, payload.OrderNo) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Find order failed", + logger.Field("error", err.Error()), + logger.Field("order_no", payload.OrderNo), + ) + return nil + } + // 1: Pending, 2: Paid, 3:Close, 4: Failed, 5:Finished + if orderInfo.Status != 2 { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Order status error", + logger.Field("order_no", orderInfo.OrderNo), + logger.Field("status", orderInfo.Status), + ) + return nil + } + switch orderInfo.Type { + case Subscribe: + err = l.NewPurchase(ctx, orderInfo) + case Renewal: + err = l.Renewal(ctx, orderInfo) + case ResetTraffic: + err = l.ResetTraffic(ctx, orderInfo) + case Recharge: + err = l.Recharge(ctx, orderInfo) + default: + logger.WithContext(ctx).Error("[ActivateOrderLogic] Order type is invalid", logger.Field("type", orderInfo.Type)) + return nil + } + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Process task failed", logger.Field("error", err.Error())) + return nil + } + // if coupon is not empty + if orderInfo.Coupon != "" { + // update coupon status + err = l.svc.CouponModel.UpdateCount(ctx, orderInfo.Coupon) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Update coupon status failed", + logger.Field("error", err.Error()), + logger.Field("coupon", orderInfo.Coupon), + ) + } + } + // update order status + orderInfo.Status = 5 + err = l.svc.OrderModel.Update(ctx, orderInfo) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Update order status failed", + logger.Field("error", err.Error()), + logger.Field("order_no", orderInfo.OrderNo), + ) + } + + return nil +} + +// NewPurchase New purchase +func (l *ActivateOrderLogic) NewPurchase(ctx context.Context, orderInfo *order.Order) error { + var userInfo *user.User + var err error + if orderInfo.UserId != 0 { + // find user by user id + userInfo, err = l.svc.UserModel.FindOne(ctx, orderInfo.UserId) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user failed", + logger.Field("error", err.Error()), + logger.Field("user_id", orderInfo.UserId), + ) + return err + } + } else { + // If User ID is 0, it means that the order is a guest order, need to create a new user + // query info with redis + cacheKey := fmt.Sprintf(constant.TempOrderCacheKey, orderInfo.OrderNo) + data, err := l.svc.Redis.Get(ctx, cacheKey).Result() + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Get temp order cache failed", + logger.Field("error", err.Error()), + logger.Field("cache_key", cacheKey), + ) + return err + } + var tempOrder constant.TemporaryOrderInfo + if err = json.Unmarshal([]byte(data), &tempOrder); err != nil { + logger.WithContext(ctx).Errorw("[ActivateOrderLogic] Unmarshal temp order failed", + logger.Field("error", err.Error()), + ) + return err + } + // create user + + userInfo = &user.User{ + Password: tool.EncodePassWord(tempOrder.Password), + AuthMethods: []user.AuthMethods{ + { + AuthType: tempOrder.AuthType, + AuthIdentifier: tempOrder.Identifier, + }, + }, + } + err = l.svc.UserModel.Transaction(ctx, func(tx *gorm.DB) error { + // Save user information + if err := tx.Save(userInfo).Error; err != nil { + return err + } + // Generate ReferCode + userInfo.ReferCode = uuidx.UserInviteCode(userInfo.Id) + // Update ReferCode + if err := tx.Model(&user.User{}).Where("id = ?", userInfo.Id).Update("refer_code", userInfo.ReferCode).Error; err != nil { + return err + } + orderInfo.UserId = userInfo.Id + return tx.Model(&order.Order{}).Where("order_no = ?", orderInfo.OrderNo).Update("user_id", userInfo.Id).Error + }) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Create user failed", + logger.Field("error", err.Error()), + ) + return err + } + + if tempOrder.InviteCode != "" { + // find referer by refer code + referer, err := l.svc.UserModel.FindOneByReferCode(ctx, tempOrder.InviteCode) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Find referer failed", + logger.Field("error", err.Error()), + logger.Field("refer_code", tempOrder.InviteCode), + ) + } else { + userInfo.RefererId = referer.Id + err = l.svc.UserModel.Update(ctx, userInfo) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Update user referer failed", + logger.Field("error", err.Error()), + logger.Field("user_id", userInfo.Id), + ) + } + } + } + + logger.WithContext(ctx).Info("[ActivateOrderLogic] Create guest user success", logger.Field("user_id", userInfo.Id), logger.Field("Identifier", tempOrder.Identifier), logger.Field("AuthType", tempOrder.AuthType)) + } + // find subscribe by id + sub, err := l.svc.SubscribeModel.FindOne(ctx, orderInfo.SubscribeId) + if err != nil { + logger.WithContext(ctx).Errorw("[ActivateOrderLogic] Find subscribe failed", + logger.Field("error", err.Error()), + logger.Field("subscribe_id", orderInfo.SubscribeId), + ) + return err + } + // create user subscribe + now := time.Now() + + userSub := user.Subscribe{ + Id: 0, + UserId: orderInfo.UserId, + OrderId: orderInfo.Id, + SubscribeId: orderInfo.SubscribeId, + StartTime: now, + ExpireTime: tool.AddTime(sub.UnitTime, orderInfo.Quantity, now), + Traffic: sub.Traffic, + Download: 0, + Upload: 0, + Token: uuidx.SubscribeToken(orderInfo.OrderNo), + UUID: uuid.New().String(), + Status: 1, + } + err = l.svc.UserModel.InsertSubscribe(ctx, &userSub) + + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Insert user subscribe failed", + logger.Field("error", err.Error()), + ) + return err + } + // handler commission + if userInfo.RefererId != 0 && + l.svc.Config.Invite.ReferralPercentage != 0 && + (!l.svc.Config.Invite.OnlyFirstPurchase || orderInfo.IsNew) { + referer, err := l.svc.UserModel.FindOne(ctx, userInfo.RefererId) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Find referer failed", + logger.Field("error", err.Error()), + logger.Field("referer_id", userInfo.RefererId), + ) + goto updateCache + } + // calculate commission + amount := float64(orderInfo.Price) * (float64(l.svc.Config.Invite.ReferralPercentage) / 100) + referer.Commission += int64(amount) + err = l.svc.UserModel.Update(ctx, referer) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Update referer commission failed", + logger.Field("error", err.Error()), + ) + goto updateCache + } + // create commission log + commissionLog := user.CommissionLog{ + UserId: referer.Id, + OrderNo: orderInfo.OrderNo, + Amount: int64(amount), + } + err = l.svc.UserModel.InsertCommissionLog(ctx, &commissionLog) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Insert commission log failed", + logger.Field("error", err.Error()), + ) + } + err = l.svc.UserModel.UpdateUserCache(ctx, referer) + if err != nil { + logger.WithContext(ctx).Errorw("[ActivateOrderLogic] Update referer cache", logger.Field("error", err.Error()), logger.Field("user_id", referer.Id)) + } + } +updateCache: + for _, id := range tool.StringToInt64Slice(sub.Server) { + cacheKey := fmt.Sprintf("%s%d", config.ServerUserListCacheKey, id) + err = l.svc.Redis.Del(ctx, cacheKey).Err() + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Del server user list cache failed", + logger.Field("error", err.Error()), + logger.Field("cache_key", cacheKey), + ) + } + } + data, err := l.svc.ServerModel.FindServerListByGroupIds(ctx, tool.StringToInt64Slice(sub.ServerGroup)) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Find server list failed", logger.Field("error", err.Error())) + return err + } + for _, item := range data { + cacheKey := fmt.Sprintf("%s%d", config.ServerUserListCacheKey, item.Id) + err = l.svc.Redis.Del(ctx, cacheKey).Err() + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Del server user list cache failed", + logger.Field("error", err.Error()), + logger.Field("cache_key", cacheKey), + ) + } + } + userTelegramChatId, ok := findTelegram(userInfo) + + // sendMessage To Telegram + if ok { + text, err := tool.RenderTemplateToString(telegram.PurchaseNotify, map[string]string{ + "OrderNo": orderInfo.OrderNo, + "SubscribeName": sub.Name, + "OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100), + "ExpireTime": userSub.ExpireTime.Format("2006-01-02 15:04:05"), + }) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Render template failed", + logger.Field("error", err.Error()), + ) + } + l.sendUserNotifyWithTelegram(userTelegramChatId, text) + } + // send message to admin + text, err := tool.RenderTemplateToString(telegram.AdminOrderNotify, map[string]string{ + "OrderNo": orderInfo.OrderNo, + "TradeNo": orderInfo.TradeNo, + "SubscribeName": sub.Name, + //"UserEmail": userInfo.Email, + "OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100), + "OrderStatus": "已支付", + "OrderTime": orderInfo.CreatedAt.Format("2006-01-02 15:04:05"), + "PaymentMethod": orderInfo.Method, + }) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Render AdminOrderNotify template failed", + logger.Field("error", err.Error()), + ) + } + l.sendAdminNotifyWithTelegram(ctx, text) + logger.WithContext(ctx).Info("[ActivateOrderLogic] Insert user subscribe success") + return nil +} + +// Renewal Renewal +func (l *ActivateOrderLogic) Renewal(ctx context.Context, orderInfo *order.Order) error { + // find user by user id + userInfo, err := l.svc.UserModel.FindOne(ctx, orderInfo.UserId) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user failed", + logger.Field("error", err.Error()), + logger.Field("user_id", orderInfo.UserId), + ) + return err + } + // find user subscribe by subscribe token + userSub, err := l.svc.UserModel.FindOneSubscribeByToken(ctx, orderInfo.SubscribeToken) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user subscribe failed", + logger.Field("error", err.Error()), + logger.Field("order_id", orderInfo.Id), + ) + return err + } + // find subscribe by id + sub, err := l.svc.SubscribeModel.FindOne(ctx, orderInfo.SubscribeId) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Find subscribe failed", + logger.Field("error", err.Error()), + logger.Field("subscribe_id", orderInfo.SubscribeId), + logger.Field("order_id", orderInfo.Id), + ) + return err + } + now := time.Now() + if userSub.ExpireTime.Before(now) { + userSub.ExpireTime = now + } + + // Check whether traffic reset on renewal is enabled + if sub.RenewalReset != nil && *sub.RenewalReset { + userSub.Download = 0 + userSub.Upload = 0 + } + if userSub.FinishedAt != nil { + userSub.FinishedAt = nil + } + + userSub.ExpireTime = tool.AddTime(sub.UnitTime, orderInfo.Quantity, userSub.ExpireTime) + userSub.Status = 1 + // update user subscribe + err = l.svc.UserModel.UpdateSubscribe(ctx, userSub) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Update user subscribe failed", + logger.Field("error", err.Error()), + ) + return err + } + // handler commission + if userInfo.RefererId != 0 && + l.svc.Config.Invite.ReferralPercentage != 0 && + !l.svc.Config.Invite.OnlyFirstPurchase { + referer, err := l.svc.UserModel.FindOne(ctx, userInfo.RefererId) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Find referer failed", + logger.Field("error", err.Error()), + logger.Field("referer_id", userInfo.RefererId), + ) + goto sendMessage + } + // calculate commission + amount := float64(orderInfo.Price) * (float64(l.svc.Config.Invite.ReferralPercentage) / 100) + referer.Commission += int64(amount) + err = l.svc.UserModel.Update(ctx, referer) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Update referer commission failed", + logger.Field("error", err.Error()), + ) + goto sendMessage + } + // create commission log + commissionLog := user.CommissionLog{ + UserId: referer.Id, + OrderNo: orderInfo.OrderNo, + Amount: int64(amount), + } + err = l.svc.UserModel.InsertCommissionLog(ctx, &commissionLog) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Insert commission log failed", + logger.Field("error", err.Error()), + ) + } + err = l.svc.UserModel.UpdateUserCache(ctx, referer) + if err != nil { + logger.WithContext(ctx).Errorw("[ActivateOrderLogic] Update referer cache", logger.Field("error", err.Error()), logger.Field("user_id", referer.Id)) + } + } + +sendMessage: + userTelegramChatId, ok := findTelegram(userInfo) + // SendMessage To Telegram + if ok { + text, err := tool.RenderTemplateToString(telegram.RenewalNotify, map[string]string{ + "OrderNo": orderInfo.OrderNo, + "SubscribeName": sub.Name, + "OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100), + "ExpireTime": userSub.ExpireTime.Format("2006-01-02 15:04:05"), + }) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Render template failed", + logger.Field("error", err.Error()), + ) + } + l.sendUserNotifyWithTelegram(userTelegramChatId, text) + } + + // send message to admin + text, err := tool.RenderTemplateToString(telegram.AdminOrderNotify, map[string]string{ + "OrderNo": orderInfo.OrderNo, + "TradeNo": orderInfo.TradeNo, + "SubscribeName": sub.Name, + //"UserEmail": userInfo.Email, + "OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100), + "OrderStatus": "已支付", + "OrderTime": orderInfo.CreatedAt.Format("2006-01-02 15:04:05"), + "PaymentMethod": orderInfo.Method, + }) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Render AdminOrderNotify template failed", + logger.Field("error", err.Error()), + ) + } + l.sendAdminNotifyWithTelegram(ctx, text) + return nil +} + +// ResetTraffic Reset traffic +func (l *ActivateOrderLogic) ResetTraffic(ctx context.Context, orderInfo *order.Order) error { + // find user by user id + userInfo, err := l.svc.UserModel.FindOne(ctx, orderInfo.UserId) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user failed", + logger.Field("error", err.Error()), + logger.Field("user_id", orderInfo.UserId), + ) + return err + } + // Generate a Subscribe Token through orderNo + // find user subscribe by subscribe token + userSub, err := l.svc.UserModel.FindOneSubscribeByToken(ctx, orderInfo.SubscribeToken) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user subscribe failed", + logger.Field("error", err.Error()), + logger.Field("order_id", orderInfo.Id), + ) + return err + } + userSub.Download = 0 + userSub.Upload = 0 + userSub.Status = 1 + // update user subscribe + err = l.svc.UserModel.UpdateSubscribe(ctx, userSub) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Update user subscribe failed", + logger.Field("error", err.Error()), + ) + return err + } + sub, err := l.svc.SubscribeModel.FindOne(ctx, userSub.SubscribeId) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Find subscribe failed", + logger.Field("error", err.Error()), + logger.Field("subscribe_id", userSub.SubscribeId), + ) + return err + } + userTelegramChatId, ok := findTelegram(userInfo) + // SendMessage To Telegram + if ok { + text, err := tool.RenderTemplateToString(telegram.ResetTrafficNotify, map[string]string{ + "OrderNo": orderInfo.OrderNo, + "SubscribeName": sub.Name, + "ResetTime": time.Now().Format("2006-01-02 15:04:05"), + "ExpireTime": userSub.ExpireTime.Format("2006-01-02 15:04:05"), + }) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Render template failed", + logger.Field("error", err.Error()), + ) + } + l.sendUserNotifyWithTelegram(userTelegramChatId, text) + } + + // send message to admin + text, err := tool.RenderTemplateToString(telegram.AdminOrderNotify, map[string]string{ + "OrderNo": orderInfo.OrderNo, + "TradeNo": orderInfo.TradeNo, + "SubscribeName": "流量重置", + "OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100), + "OrderStatus": "已支付", + "OrderTime": orderInfo.CreatedAt.Format("2006-01-02 15:04:05"), + "PaymentMethod": orderInfo.Method, + }) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Render AdminOrderNotify template failed", + logger.Field("error", err.Error()), + ) + } + l.sendAdminNotifyWithTelegram(ctx, text) + return nil +} + +// Recharge Recharge to user +func (l *ActivateOrderLogic) Recharge(ctx context.Context, orderInfo *order.Order) error { + // find user by user id + userInfo, err := l.svc.UserModel.FindOne(ctx, orderInfo.UserId) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user failed", + logger.Field("error", err.Error()), + logger.Field("user_id", orderInfo.UserId), + ) + return err + } + userInfo.Balance += orderInfo.Price + // update user + err = l.svc.DB.Transaction(func(tx *gorm.DB) error { + err = l.svc.UserModel.Update(ctx, userInfo, tx) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Update user failed", + logger.Field("error", err.Error()), + ) + return err + } + // Create Balance Log + balanceLog := user.BalanceLog{ + UserId: orderInfo.UserId, + Amount: orderInfo.Price, + Type: 1, + OrderId: orderInfo.Id, + Balance: userInfo.Balance, + } + err = l.svc.UserModel.InsertBalanceLog(ctx, &balanceLog, tx) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Insert balance log failed", + logger.Field("error", err.Error()), + ) + return err + } + + return nil + }) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Database transaction failed", + logger.Field("error", err.Error()), + ) + return err + } + userTelegramChatId, ok := findTelegram(userInfo) + // SendMessage To Telegram + if ok { + text, err := tool.RenderTemplateToString(telegram.RechargeNotify, map[string]string{ + "OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100), + "PaymentMethod": orderInfo.Method, + "Time": orderInfo.CreatedAt.Format("2006-01-02 15:04:05"), + "Balance": fmt.Sprintf("%.2f", float64(userInfo.Balance)/100), + }) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Render template failed", + logger.Field("error", err.Error()), + ) + } + l.sendUserNotifyWithTelegram(userTelegramChatId, text) + } + // send message to admin + text, err := tool.RenderTemplateToString(telegram.AdminOrderNotify, map[string]string{ + "OrderNo": orderInfo.OrderNo, + "TradeNo": orderInfo.TradeNo, + "OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100), + "SubscribeName": "余额充值", + "OrderStatus": "已支付", + "OrderTime": orderInfo.CreatedAt.Format("2006-01-02 15:04:05"), + "PaymentMethod": orderInfo.Method, + }) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Render AdminOrderNotify template failed", + logger.Field("error", err.Error()), + ) + } + l.sendAdminNotifyWithTelegram(ctx, text) + return nil +} + +// sendUserNotifyWithTelegram send message to user +func (l *ActivateOrderLogic) sendUserNotifyWithTelegram(chatId int64, text string) { + msg := tgbotapi.NewMessage(chatId, text) + msg.ParseMode = "markdown" + _, err := l.svc.TelegramBot.Send(msg) + if err != nil { + logger.Error("[ActivateOrderLogic] Send telegram user message failed", + logger.Field("error", err.Error()), + ) + } +} + +// sendAdminNotifyWithTelegram send message to admin +func (l *ActivateOrderLogic) sendAdminNotifyWithTelegram(ctx context.Context, text string) { + admins, err := l.svc.UserModel.QueryAdminUsers(ctx) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Query admin users failed", + logger.Field("error", err.Error()), + ) + return + } + for _, admin := range admins { + telegramId, ok := findTelegram(admin) + if !ok { + continue + } + msg := tgbotapi.NewMessage(telegramId, text) + msg.ParseMode = "markdown" + _, err := l.svc.TelegramBot.Send(msg) + if err != nil { + logger.WithContext(ctx).Error("[ActivateOrderLogic] Send telegram admin message failed", + logger.Field("error", err.Error()), + ) + } + } +} + +// findTelegram find user telegram id +func findTelegram(u *user.User) (int64, bool) { + for _, item := range u.AuthMethods { + if item.AuthType == "telegram" { + // string to int64 + parseInt, err := strconv.ParseInt(item.AuthIdentifier, 10, 64) + if err != nil { + return 0, false + } + return parseInt, true + } + + } + return 0, false +} diff --git a/queue/logic/order/checkOrderLogic.go b/queue/logic/order/checkOrderLogic.go deleted file mode 100644 index 6af678b..0000000 --- a/queue/logic/order/checkOrderLogic.go +++ /dev/null @@ -1,161 +0,0 @@ -package orderLogic - -import ( - "context" - "encoding/json" - "github.com/hibiken/asynq" - order2 "github.com/perfect-panel/ppanel-server/internal/logic/public/order" - "github.com/perfect-panel/ppanel-server/internal/model/payment" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/pkg/payment/alipay" - "github.com/perfect-panel/ppanel-server/pkg/payment/payssion" - "github.com/perfect-panel/ppanel-server/pkg/payment/stripe" - "github.com/perfect-panel/ppanel-server/queue/types" - "go.uber.org/zap" -) - -type CheckOrderLogic struct { - svc *svc.ServiceContext -} - -func NewCheckOrderLogic(svc *svc.ServiceContext) *CheckOrderLogic { - return &CheckOrderLogic{ - svc: svc, - } -} - -func (l *CheckOrderLogic) ProcessTask(ctx context.Context, task *asynq.Task) error { - - orderList, err := l.svc.OrderModel.QueryPendingOrders(ctx) - if err != nil { - logger.Errorf("query pending orders error: %v", zap.Error(err)) - return err - } - logger.Infof("查到订单数据: %v", orderList) - for _, order := range orderList { - paymentConfig, err := l.svc.PaymentModel.FindOne(ctx, order.PaymentId) - if err != nil { - logger.Errorw("[CheckOrder] Find payment config failed", logger.Field("error", err.Error()), logger.Field("paymentMark", order.Method)) - continue - } - logger.Infof("查到配置数据[%s]: %v", order.Method, orderList) - var flag bool - switch order.Method { - case order2.AlipayF2f: - if l.queryAlipay(paymentConfig, order.TradeNo) { - flag = true - } - break - case order2.Payssion: - logger.Infof("匹配配置类型: %v", order2.Payssion) - if l.queryPayssion(paymentConfig, order.OrderNo) { - flag = true - } - break - case order2.StripeWeChatPay: - if l.queryStripe(paymentConfig, order.TradeNo) { - flag = true - } - break - default: - logger.Infow("[CheckOrder] Unsupported payment method", logger.Field("paymentMethod", order.Method)) - continue - } - logger.Infof("[CheckOrder] Unsupported payment method[%v]", flag) - if flag { - err := l.svc.OrderModel.UpdateOrderStatus(ctx, order.OrderNo, 2) - if err != nil { - logger.Errorf("[CheckOrder] query order status error: %v", zap.Error(err)) - } - logger.Info("[CheckOrder] Notify status success", logger.Field("orderNo", order.TradeNo)) - payload := types.ForthwithActivateOrderPayload{ - OrderNo: order.OrderNo, - } - bytes, err := json.Marshal(&payload) - if err != nil { - logger.Error("[CheckOrder] Marshal payload failed", logger.Field("error", err.Error())) - return err - } - task := asynq.NewTask(types.ForthwithActivateOrder, bytes) - taskInfo, err := l.svc.Queue.EnqueueContext(ctx, task) - if err != nil { - logger.Error("[CheckOrder] Enqueue task failed", logger.Field("error", err.Error())) - return err - } - logger.Info("[CheckOrder] Enqueue task success", logger.Field("taskInfo", taskInfo)) - } - } - - return nil -} - -// queryAlipay Query Alipay payment status -// -//nolint:unused -func (l *CheckOrderLogic) queryAlipay(paymentConfig *payment.Payment, TradeNo string) bool { - config := payment.AlipayF2FConfig{} - if err := json.Unmarshal([]byte(paymentConfig.Config), &config); err != nil { - zap.S().Errorw("[CheckOrder] Unmarshal payment config failed", logger.Field("error", err.Error()), logger.Field("config", paymentConfig.Config)) - return false - } - client := alipay.NewClient(alipay.Config{ - AppId: config.AppId, - PrivateKey: config.PrivateKey, - PublicKey: config.PublicKey, - InvoiceName: config.InvoiceName, - }) - status, err := client.QueryTrade(context.Background(), TradeNo) - if err != nil { - zap.S().Errorw("[CheckOrder] Query trade failed", logger.Field("error", err.Error()), logger.Field("TradeNo", TradeNo)) - return false - } - if status == alipay.Success || status == alipay.Finished { - return true - } - return false -} - -// queryStripe Query Stripe payment status -// -//nolint:unused -func (l *CheckOrderLogic) queryStripe(paymentConfig *payment.Payment, TradeNo string) bool { - config := payment.StripeConfig{} - if err := json.Unmarshal([]byte(paymentConfig.Config), &config); err != nil { - zap.S().Errorw("[CheckOrder] Unmarshal payment config failed", logger.Field("error", err.Error()), logger.Field("config", paymentConfig.Config)) - return false - } - client := stripe.NewClient(stripe.Config{ - PublicKey: config.PublicKey, - SecretKey: config.SecretKey, - WebhookSecret: config.WebhookSecret, - }) - status, err := client.QueryOrderStatus(TradeNo) - if err != nil { - zap.S().Errorw("[CheckOrder] Query order status failed", logger.Field("error", err.Error()), logger.Field("TradeNo", TradeNo)) - return false - } - return status -} - -// queryPayssion Query Stripe payment status -// -//nolint:unused -func (l *CheckOrderLogic) queryPayssion(paymentConfig *payment.Payment, TradeNo string) bool { - zap.S().Infof("[CheckOrder]1 Query Payssion called") - payssionConfig := payment.PayssionConfig{} - if err := json.Unmarshal([]byte(paymentConfig.Config), &payssionConfig); err != nil { - zap.S().Errorw("[CheckOrder] Unmarshal error", logger.Field("error", err.Error())) - return false - } - zap.S().Infof("[CheckOrder]2 Query Payssion called") - client := payssion.NewClient(payssionConfig.ApiKey, payssionConfig.SecretKey, payssionConfig.PmId, payssionConfig.Currency, payssionConfig.QueryUrl, payssionConfig.CreateUrl) - // create payment - result, err := client.QueryOrder(TradeNo) - if err != nil { - zap.S().Errorw("[CheckOrder] Query order status failed", logger.Field("error", err.Error()), logger.Field("TradeNo", TradeNo)) - return false - } - zap.S().Infof("[CheckOrder]3 Query Payssion called") - return result.Transaction.State == "completed" -} diff --git a/queue/logic/order/deferCloseOrderLogic.go b/queue/logic/order/deferCloseOrderLogic.go index 16a446e..1dc91f3 100644 --- a/queue/logic/order/deferCloseOrderLogic.go +++ b/queue/logic/order/deferCloseOrderLogic.go @@ -4,13 +4,13 @@ import ( "context" "encoding/json" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/logic/public/order" - "github.com/perfect-panel/ppanel-server/internal/svc" - internal "github.com/perfect-panel/ppanel-server/internal/types" - "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/logic/public/order" + "github.com/perfect-panel/server/internal/svc" + internal "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/queue/types" ) type DeferCloseOrderLogic struct { diff --git a/queue/logic/sms/sendSmsLogic.go b/queue/logic/sms/sendSmsLogic.go index ad61b9d..76cef6d 100644 --- a/queue/logic/sms/sendSmsLogic.go +++ b/queue/logic/sms/sendSmsLogic.go @@ -4,15 +4,16 @@ import ( "context" "encoding/json" "fmt" + "time" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/log" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/constant" - "github.com/perfect-panel/ppanel-server/pkg/sms" - "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/constant" + "github.com/perfect-panel/server/pkg/sms" + "github.com/perfect-panel/server/queue/types" ) type SmsSendCount struct { @@ -43,17 +44,16 @@ func (l *SendSmsLogic) ProcessTask(ctx context.Context, task *asynq.Task) error logger.WithContext(ctx).Error("[SendSmsLogic] New send sms client failed", logger.Field("error", err.Error()), logger.Field("payload", payload)) return err } - createSms := &log.MessageLog{ - Type: log.Mobile.String(), + createSms := &log.Message{ Platform: l.svcCtx.Config.Mobile.Platform, To: fmt.Sprintf("+%s%s", payload.TelephoneArea, payload.Telephone), Subject: constant.ParseVerifyType(payload.Type).String(), - Content: "", + Content: map[string]interface{}{ + "content": client.GetSendCodeContent(payload.Content), + }, } err = client.SendCode(payload.TelephoneArea, payload.Telephone, payload.Content) - createSms.Content = client.GetSendCodeContent(payload.Content) - if err != nil { logger.WithContext(ctx).Error("[SendSmsLogic] Send sms failed", logger.Field("error", err.Error()), logger.Field("payload", payload)) if l.svcCtx.Config.Model != constant.DevMode { @@ -64,7 +64,14 @@ func (l *SendSmsLogic) ProcessTask(ctx context.Context, task *asynq.Task) error } createSms.Status = 1 logger.WithContext(ctx).Info("[SendSmsLogic] Send sms", logger.Field("telephone", payload.Telephone), logger.Field("content", createSms.Content)) - err = l.svcCtx.LogModel.InsertMessageLog(ctx, createSms) + + content, _ := createSms.Marshal() + err = l.svcCtx.LogModel.Insert(ctx, &log.SystemLog{ + Type: log.TypeMobileMessage.Uint8(), + Date: time.Now().Format("2006-01-02"), + ObjectID: 0, + Content: string(content), + }) if err != nil { logger.WithContext(ctx).Error("[SendSmsLogic] Send sms failed", logger.Field("error", err.Error()), logger.Field("payload", payload)) return nil diff --git a/queue/logic/subscription/checkSubscriptionLogic.go b/queue/logic/subscription/checkSubscriptionLogic.go index 8cfe566..81b86e7 100644 --- a/queue/logic/subscription/checkSubscriptionLogic.go +++ b/queue/logic/subscription/checkSubscriptionLogic.go @@ -1,19 +1,17 @@ package subscription import ( - "bytes" "context" "encoding/json" - "text/template" "time" - queue "github.com/perfect-panel/ppanel-server/queue/types" + queue "github.com/perfect-panel/server/queue/types" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/user" - "github.com/perfect-panel/ppanel-server/internal/svc" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/internal/svc" "gorm.io/gorm" ) @@ -32,7 +30,7 @@ func (l *CheckSubscriptionLogic) ProcessTask(ctx context.Context, _ *asynq.Task) // Check subscription traffic err := l.svc.UserModel.Transaction(ctx, func(db *gorm.DB) error { var list []*user.Subscribe - err := db.Model(&user.Subscribe{}).Where("upload + download >= traffic AND status = 1 AND traffic > 0 ").Find(&list).Error + err := db.Model(&user.Subscribe{}).Where("upload + download >= traffic AND status IN (0, 1) AND traffic > 0 ").Find(&list).Error if err != nil { logger.Errorw("[Check Subscription Traffic] Query subscribe failed", logger.Field("error", err.Error())) return err @@ -62,7 +60,7 @@ func (l *CheckSubscriptionLogic) ProcessTask(ctx context.Context, _ *asynq.Task) return err } } - + l.clearServerCache(ctx, list...) logger.Infow("[Check Subscription Traffic] Update subscribe status", logger.Field("user_ids", ids), logger.Field("count", int64(len(ids)))) } else { @@ -77,7 +75,7 @@ func (l *CheckSubscriptionLogic) ProcessTask(ctx context.Context, _ *asynq.Task) // Check subscription expire err = l.svc.UserModel.Transaction(ctx, func(db *gorm.DB) error { var list []*user.Subscribe - err = db.Model(&user.Subscribe{}).Where("`status` = 1 AND `expire_time` < ? AND `expire_time` != ? and `finished_at` IS NULL", time.Now(), time.UnixMilli(0)).Find(&list).Error + err = db.Model(&user.Subscribe{}).Where("`status` IN (0, 1) AND `expire_time` < ? AND `expire_time` != ? and `finished_at` IS NULL", time.Now(), time.UnixMilli(0)).Find(&list).Error if err != nil { logger.Error("[Check Subscription] Find subscribe failed", logger.Field("error", err.Error())) return err @@ -87,7 +85,10 @@ func (l *CheckSubscriptionLogic) ProcessTask(ctx context.Context, _ *asynq.Task) ids = append(ids, item.Id) } if len(ids) > 0 { - err = db.Model(&user.Subscribe{}).Where("id IN ?", ids).Update("status", 3).Error + err = db.Model(&user.Subscribe{}).Where("id IN ?", ids).Updates(map[string]interface{}{ + "status": 3, + "finished_at": time.Now(), + }).Error if err != nil { logger.Error("[Check Subscription Expire] Update subscribe status failed", logger.Field("error", err.Error())) return err @@ -97,17 +98,17 @@ func (l *CheckSubscriptionLogic) ProcessTask(ctx context.Context, _ *asynq.Task) logger.Error("[Check Subscription Expire] Send email failed", logger.Field("error", err.Error())) return nil } - if len(list) > 0 { - if err = l.svc.UserModel.ClearSubscribeCache(ctx, list...); err != nil { - logger.Errorw("[Check Subscription Traffic] Clear subscribe cache failed", logger.Field("error", err.Error())) - return err - } + if err = l.svc.UserModel.ClearSubscribeCache(ctx, list...); err != nil { + logger.Errorw("[Check Subscription Traffic] Clear subscribe cache failed", logger.Field("error", err.Error())) + return err } + l.clearServerCache(ctx, list...) + logger.Info("[Check Subscription Expire] Update subscribe status", logger.Field("user_ids", ids), logger.Field("count", int64(len(ids)))) } else { logger.Info("[Check Subscription Expire] No subscribe need to update") } - return l.svc.UserModel.ClearSubscribeCache(ctx, list...) + return nil }) if err != nil { logger.Info("[CheckSubscription] Transaction failed", logger.Field("error", err.Error())) @@ -128,24 +129,14 @@ func (l *CheckSubscriptionLogic) sendExpiredNotify(ctx context.Context, subs []i continue } var taskPayload queue.SendEmailPayload + taskPayload.Type = queue.EmailTypeExpiration taskPayload.Email = method.AuthIdentifier taskPayload.Subject = "Subscription Expired" - tpl, err := template.New("Expired").Parse(l.svc.Config.Email.ExpirationEmailTemplate) - if err != nil { - logger.Errorw("[CheckSubscription] Parse template failed", logger.Field("error", err.Error())) - continue - } - var result bytes.Buffer - err = tpl.Execute(&result, map[string]interface{}{ + taskPayload.Content = map[string]interface{}{ "SiteLogo": l.svc.Config.Site.SiteLogo, "SiteName": l.svc.Config.Site.SiteName, "ExpireDate": sub.ExpireTime.Format("2006-01-02 15:04:05"), - }) - if err != nil { - logger.Errorw("[CheckSubscription] Execute template failed", logger.Field("error", err.Error())) - continue } - taskPayload.Content = result.String() payloadBuy, err := json.Marshal(taskPayload) if err != nil { logger.Errorw("[CheckSubscription] Marshal payload failed", logger.Field("error", err.Error())) @@ -178,23 +169,13 @@ func (l *CheckSubscriptionLogic) sendTrafficNotify(ctx context.Context, subs []i continue } var taskPayload queue.SendEmailPayload + taskPayload.Type = queue.EmailTypeTrafficExceed taskPayload.Email = method.AuthIdentifier taskPayload.Subject = "Subscription Traffic Exceed" - tpl, err := template.New("Traffic").Parse(l.svc.Config.Email.TrafficExceedEmailTemplate) - if err != nil { - logger.Errorw("[CheckSubscription] Parse template failed", logger.Field("error", err.Error())) - continue - } - var result bytes.Buffer - err = tpl.Execute(&result, map[string]interface{}{ + taskPayload.Content = map[string]interface{}{ "SiteLogo": l.svc.Config.Site.SiteLogo, "SiteName": l.svc.Config.Site.SiteName, - }) - if err != nil { - logger.Errorw("[CheckSubscription] Execute template failed", logger.Field("error", err.Error())) - continue } - taskPayload.Content = result.String() payloadBuy, err := json.Marshal(taskPayload) if err != nil { logger.Errorw("[CheckSubscription] Marshal payload failed", logger.Field("error", err.Error())) @@ -213,3 +194,18 @@ func (l *CheckSubscriptionLogic) sendTrafficNotify(ctx context.Context, subs []i } return nil } + +func (l *CheckSubscriptionLogic) clearServerCache(ctx context.Context, userSubs ...*user.Subscribe) { + subs := make(map[int64]bool) + for _, sub := range userSubs { + if _, ok := subs[sub.SubscribeId]; !ok { + subs[sub.SubscribeId] = true + } + } + + for sub, _ := range subs { + if err := l.svc.SubscribeModel.ClearCache(ctx, sub); err != nil { + logger.Errorw("[CheckSubscription] ClearCache failed", logger.Field("error", err.Error()), logger.Field("subscribe_id", sub)) + } + } +} diff --git a/queue/logic/task/quotaLogic.go b/queue/logic/task/quotaLogic.go new file mode 100644 index 0000000..c2b1464 --- /dev/null +++ b/queue/logic/task/quotaLogic.go @@ -0,0 +1,407 @@ +package task + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "time" + + "github.com/hibiken/asynq" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/internal/model/task" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/tool" + "gorm.io/gorm" +) + +const ( + UnitTimeNoLimit = "NoLimit" // Unlimited time subscription + UnitTimeYear = "Year" // Annual subscription + UnitTimeMonth = "Month" // Monthly subscription + UnitTimeDay = "Day" // Daily subscription + UnitTimeHour = "Hour" // Hourly subscription + UnitTimeMinute = "Minute" // Per-minute subscription + +) + +type QuotaTaskLogic struct { + svcCtx *svc.ServiceContext +} + +type ErrorInfo struct { + UserSubscribeId int64 `json:"user_subscribe_id"` + Error string `json:"error"` +} + +func NewQuotaTaskLogic(svcCtx *svc.ServiceContext) *QuotaTaskLogic { + return &QuotaTaskLogic{ + svcCtx: svcCtx, + } +} + +func (l *QuotaTaskLogic) ProcessTask(ctx context.Context, t *asynq.Task) error { + taskID, err := l.parseTaskID(ctx, t.Payload()) + if err != nil { + return err + } + + taskInfo, err := l.getTaskInfo(ctx, taskID) + if err != nil { + return err + } + + if taskInfo.Status != 0 { + logger.WithContext(ctx).Info("[QuotaTaskLogic.ProcessTask] task already processed", + logger.Field("taskID", taskID), + logger.Field("status", taskInfo.Status), + ) + return nil + } + + scope, content, err := l.parseTaskData(ctx, taskInfo) + if err != nil { + return err + } + + subscribes, err := l.getSubscribes(ctx, scope.Objects) + if err != nil { + return err + } + if err = l.processSubscribes(ctx, subscribes, content, taskInfo); err != nil { + return err + } + // 清理用户缓存(仅在有赠送金时清理) + if content.GiftValue != 0 { + var userIds []int64 + for _, sub := range subscribes { + userIds = append(userIds, sub.UserId) + } + userIds = tool.RemoveDuplicateElements(userIds...) + var users []*user.User + if err = l.svcCtx.DB.WithContext(ctx).Model(&user.User{}).Where("id IN ?", userIds).Find(&users).Error; err != nil { + logger.WithContext(ctx).Error("[QuotaTaskLogic.ProcessTask] find users error", + logger.Field("error", err.Error()), + logger.Field("userIDs", userIds)) + } + err = l.svcCtx.UserModel.ClearUserCache(ctx, users...) + if err != nil { + logger.WithContext(ctx).Error("[QuotaTaskLogic.ProcessTask] clear user cache error", + logger.Field("error", err.Error()), + logger.Field("userIDs", userIds)) + } + } + + // 清理用户订阅缓存 + err = l.svcCtx.UserModel.ClearSubscribeCache(ctx, subscribes...) + if err != nil { + logger.WithContext(ctx).Error("[QuotaTaskLogic.ProcessTask] clear subscribe cache error", + logger.Field("error", err.Error())) + } + + return nil +} + +func (l *QuotaTaskLogic) parseTaskID(ctx context.Context, payload []byte) (int64, error) { + if len(payload) == 0 { + logger.WithContext(ctx).Error("[QuotaTaskLogic.parseTaskID] empty payload") + return 0, asynq.SkipRetry + } + + taskID, err := strconv.ParseInt(string(payload), 10, 64) + if err != nil { + logger.WithContext(ctx).Error("[QuotaTaskLogic.parseTaskID] invalid task ID", + logger.Field("error", err.Error()), + logger.Field("payload", string(payload)), + ) + return 0, asynq.SkipRetry + } + return taskID, nil +} + +func (l *QuotaTaskLogic) getTaskInfo(ctx context.Context, taskID int64) (*task.Task, error) { + var taskInfo *task.Task + if err := l.svcCtx.DB.WithContext(ctx).Model(&task.Task{}).Where("id = ?", taskID).First(&taskInfo).Error; err != nil { + logger.WithContext(ctx).Error("[QuotaTaskLogic.getTaskInfo] find task error", + logger.Field("error", err.Error()), + logger.Field("taskID", taskID), + ) + return nil, asynq.SkipRetry + } + return taskInfo, nil +} + +func (l *QuotaTaskLogic) parseTaskData(ctx context.Context, taskInfo *task.Task) (task.QuotaScope, task.QuotaContent, error) { + var scope task.QuotaScope + if err := scope.Unmarshal([]byte(taskInfo.Scope)); err != nil { + logger.WithContext(ctx).Error("[QuotaTaskLogic.parseTaskData] unmarshal scope error", + logger.Field("error", err.Error()), + ) + return scope, task.QuotaContent{}, asynq.SkipRetry + } + + var content task.QuotaContent + if err := content.Unmarshal([]byte(taskInfo.Content)); err != nil { + logger.WithContext(ctx).Error("[QuotaTaskLogic.parseTaskData] unmarshal content error", + logger.Field("error", err.Error()), + ) + return scope, content, asynq.SkipRetry + } + return scope, content, nil +} + +func (l *QuotaTaskLogic) getSubscribes(ctx context.Context, subscriberIDs []int64) ([]*user.Subscribe, error) { + var subscribes []*user.Subscribe + if err := l.svcCtx.DB.WithContext(ctx).Model(&user.Subscribe{}).Where("id IN ?", subscriberIDs).Find(&subscribes).Error; err != nil { + logger.WithContext(ctx).Error("[QuotaTaskLogic.getSubscribes] find subscribes error", + logger.Field("error", err.Error()), + logger.Field("subscribers", subscriberIDs), + ) + return nil, asynq.SkipRetry + } + return subscribes, nil +} + +func (l *QuotaTaskLogic) processSubscribes(ctx context.Context, subscribes []*user.Subscribe, content task.QuotaContent, taskInfo *task.Task) error { + tx := l.svcCtx.DB.WithContext(ctx).Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + logger.WithContext(ctx).Error("[QuotaTaskLogic.processSubscribes] transaction panic", + logger.Field("panic", r), + ) + } + }() + + var errors []ErrorInfo + now := time.Now() + + for _, sub := range subscribes { + if err := l.processSubscription(tx, sub, content, now, &errors); err != nil { + tx.Rollback() + return err + } + } + + // 根据错误情况决定任务状态 + status := int8(2) // Completed + if len(errors) > 0 { + logger.WithContext(ctx).Error("[QuotaTaskLogic.processSubscribes] some subscriptions failed", + logger.Field("total", len(subscribes)), + logger.Field("failed", len(errors)), + ) + // 如果所有订阅都失败,标记为失败状态 + if len(errors) == len(subscribes) { + status = 3 // Failed + } + errs, err := json.Marshal(errors) + if err != nil { + logger.WithContext(ctx).Error("[QuotaTaskLogic.processSubscribes] marshal errors failed", + logger.Field("error", err.Error()), + ) + tx.Rollback() + return err + } + taskInfo.Errors = string(errs) + } + + taskInfo.Current = uint64(len(subscribes)) + taskInfo.Status = status + err := tx.Where("id = ?", taskInfo.Id).Save(taskInfo).Error + if err != nil { + logger.WithContext(ctx).Error("[QuotaTaskLogic.processSubscribes] update task status error", + logger.Field("error", err.Error()), + logger.Field("taskID", taskInfo.Id), + ) + tx.Rollback() + return err + } + + if err = tx.Commit().Error; err != nil { + logger.WithContext(ctx).Error("[QuotaTaskLogic.processSubscribes] commit transaction error", + logger.Field("error", err.Error()), + ) + return err + } + + return nil +} + +func (l *QuotaTaskLogic) processSubscription(tx *gorm.DB, sub *user.Subscribe, content task.QuotaContent, now time.Time, errors *[]ErrorInfo) error { + // 验证订阅数据 + if sub == nil { + *errors = append(*errors, ErrorInfo{ + UserSubscribeId: 0, + Error: "subscription is nil", + }) + return nil + } + + updated := false + + // 处理时间延长 - 修复逻辑:只要Days不为0就处理,不管ExpireTime是否为0 + if content.Days != 0 { + if sub.ExpireTime.Unix() == 0 || sub.ExpireTime.Before(now) { + // 如果没有过期时间或已过期,从现在开始计算 + sub.ExpireTime = now.AddDate(0, 0, int(content.Days)) + } else { + // 在原有过期时间基础上延长 + sub.ExpireTime = sub.ExpireTime.AddDate(0, 0, int(content.Days)) + } + // 如果订阅延长到未来时间,设置为激活状态 + if sub.ExpireTime.After(now) && sub.Status != 1 { + sub.Status = 1 // Active + } + updated = true + } + + // 处理流量重置 + if content.ResetTraffic { + sub.Download = 0 + sub.Upload = 0 + updated = true + if err := l.createResetTrafficLog(tx, sub.Id, sub.UserId, now); err != nil { + // 记录错误但不阻断整个任务,日志失败不影响主流程 + *errors = append(*errors, ErrorInfo{ + UserSubscribeId: sub.Id, + Error: "create reset traffic log error: " + err.Error(), + }) + } + } + + // 处理赠送金 + if content.GiftValue != 0 { + if err := l.processGift(tx, sub, content, now, errors); err != nil { + return err + } + } + + // 只有在有更新时才保存订阅信息 + if updated { + if err := tx.Where("id = ?", sub.Id).Save(sub).Error; err != nil { + *errors = append(*errors, ErrorInfo{ + UserSubscribeId: sub.Id, + Error: "update subscription error: " + err.Error(), + }) + return nil + } + } + + return nil +} + +func (l *QuotaTaskLogic) processGift(tx *gorm.DB, sub *user.Subscribe, content task.QuotaContent, now time.Time, errors *[]ErrorInfo) error { + // 验证赠送类型 + if content.GiftType != 1 && content.GiftType != 2 { + *errors = append(*errors, ErrorInfo{ + UserSubscribeId: sub.Id, + Error: fmt.Sprintf("invalid gift type: %d", content.GiftType), + }) + return nil + } + + var userInfo user.User + if err := tx.Model(&user.User{}).Where("id = ?", sub.UserId).First(&userInfo).Error; err != nil { + *errors = append(*errors, ErrorInfo{ + UserSubscribeId: sub.Id, + Error: "find user error: " + err.Error(), + }) + return nil + } + + var giftAmount int64 + switch content.GiftType { + case 1: + giftAmount = int64(content.GiftValue) + case 2: + // 获取订阅对应的套餐信息 + subscribeInfo, err := l.svcCtx.SubscribeModel.FindOne(context.Background(), sub.SubscribeId) + if err != nil { + *errors = append(*errors, ErrorInfo{ + UserSubscribeId: sub.Id, + Error: "find subscribe error: " + err.Error(), + }) + return nil + } + if subscribeInfo.UnitPrice > 0 { + giftAmount = int64(float64(subscribeInfo.UnitPrice) * (float64(content.GiftValue) / 100)) + } + } + + if giftAmount > 0 { + userInfo.GiftAmount += giftAmount + // 使用Update而不是Save,更精确地更新单个字段 + if err := tx.Model(&user.User{}).Where("id = ?", sub.UserId).Update("gift_amount", userInfo.GiftAmount).Error; err != nil { + *errors = append(*errors, ErrorInfo{ + UserSubscribeId: sub.Id, + Error: "update user gift amount error: " + err.Error(), + }) + return nil + } + + if err := l.createGiftLog(tx, sub.Id, userInfo.Id, giftAmount, userInfo.GiftAmount, now); err != nil { + *errors = append(*errors, ErrorInfo{ + UserSubscribeId: sub.Id, + Error: "create gift log error: " + err.Error(), + }) + // 回滚用户金额更新 + userInfo.GiftAmount -= giftAmount + tx.Model(&user.User{}).Where("id = ?", sub.UserId).Update("gift_amount", userInfo.GiftAmount) + return nil + } + } + + return nil +} + +func (l *QuotaTaskLogic) getStartTime(sub *user.Subscribe, now time.Time) time.Time { + if sub.StartTime.Unix() == 0 { + return now + } + return sub.StartTime +} + +func (l *QuotaTaskLogic) createGiftLog(tx *gorm.DB, subscribeId, userId, amount, balance int64, now time.Time) error { + giftLog := &log.Gift{ + Type: log.GiftTypeIncrease, + OrderNo: "", + SubscribeId: subscribeId, + Amount: amount, + Balance: balance, + Remark: "Quota task gift", + Timestamp: now.UnixMilli(), + } + + logString, err := giftLog.Marshal() + if err != nil { + return fmt.Errorf("marshal gift log error: %v", err) + } + return tx.Model(&log.SystemLog{}).Create(&log.SystemLog{ + Type: log.TypeGift.Uint8(), + Content: string(logString), + ObjectID: userId, + Date: now.Format(time.DateOnly), + }).Error +} + +func (l *QuotaTaskLogic) createResetTrafficLog(tx *gorm.DB, subscribeId, userId int64, now time.Time) error { + trafficLog := &log.ResetSubscribe{ + Type: log.ResetSubscribeTypeQuota, + UserId: userId, + OrderNo: "", + Timestamp: now.UnixMilli(), + } + + logString, err := trafficLog.Marshal() + if err != nil { + return fmt.Errorf("marshal traffic log error: %v", err) + } + return tx.Model(&log.SystemLog{}).Create(&log.SystemLog{ + Type: log.TypeResetSubscribe.Uint8(), + Content: string(logString), + ObjectID: subscribeId, + Date: now.Format(time.DateOnly), + }).Error +} diff --git a/queue/logic/traffic/resetTrafficLogic.go b/queue/logic/traffic/resetTrafficLogic.go new file mode 100644 index 0000000..bbfee15 --- /dev/null +++ b/queue/logic/traffic/resetTrafficLogic.go @@ -0,0 +1,625 @@ +package traffic + +import ( + "context" + "encoding/json" + "errors" + "strconv" + "strings" + "time" + + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/internal/model/subscribe" + "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/queue/types" + + "github.com/hibiken/asynq" + "github.com/redis/go-redis/v9" + "gorm.io/gorm" +) + +// ResetTrafficLogic handles traffic reset logic for different subscription cycles +// Supports three reset modes: +// - reset_cycle = 1: Reset on 1st of every month +// - reset_cycle = 2: Reset monthly based on subscription start date +// - reset_cycle = 3: Reset yearly based on subscription start date +type ResetTrafficLogic struct { + svc *svc.ServiceContext +} + +// Cache and retry configuration constants +const ( + maxRetryAttempts = 3 + retryDelay = 30 * time.Minute + lockTimeout = 5 * time.Minute +) + +// Cache keys +var ( + cacheKey = "reset_traffic_cache" + retryCountKey = "reset_traffic_retry_count" + lockKey = "reset_traffic_lock" +) + +// resetTrafficCache stores the last reset time to prevent duplicate processing +type resetTrafficCache struct { + LastResetTime time.Time +} + +func NewResetTrafficLogic(svc *svc.ServiceContext) *ResetTrafficLogic { + return &ResetTrafficLogic{ + svc: svc, + } +} + +// ProcessTask executes the traffic reset task for all subscription types with enhanced retry mechanism +func (l *ResetTrafficLogic) ProcessTask(ctx context.Context, _ *asynq.Task) error { + var err error + startTime := time.Now() + + // Get current retry count + retryCount := l.getRetryCount(ctx) + logger.Infow("[ResetTraffic] Starting task execution", + logger.Field("retryCount", retryCount), + logger.Field("startTime", startTime)) + + // Acquire distributed lock to prevent duplicate execution + lockAcquired := l.acquireLock(ctx) + if !lockAcquired { + logger.Infow("[ResetTraffic] Another task is already running, skipping execution") + return nil + } + defer l.releaseLock(ctx) + + defer func() { + if err != nil { + // Check if error is retryable and within retry limit + if l.isRetryableError(err) && retryCount < maxRetryAttempts { + // Increment retry count + l.setRetryCount(ctx, retryCount+1) + + // Schedule retry with delay + task := asynq.NewTask(types.SchedulerResetTraffic, nil) + _, retryErr := l.svc.Queue.Enqueue(task, asynq.ProcessIn(retryDelay)) + if retryErr != nil { + logger.Errorw("[ResetTraffic] Failed to enqueue retry task", + logger.Field("error", retryErr.Error()), + logger.Field("retryCount", retryCount)) + } else { + logger.Infow("[ResetTraffic] Task failed, retrying in 30 minutes", + logger.Field("error", err.Error()), + logger.Field("retryCount", retryCount+1), + logger.Field("maxRetryAttempts", maxRetryAttempts)) + } + } else { + // Max retries reached or non-retryable error + if retryCount >= maxRetryAttempts { + logger.Errorw("[ResetTraffic] Max retry attempts reached, giving up", + logger.Field("retryCount", retryCount), + logger.Field("maxRetryAttempts", maxRetryAttempts), + logger.Field("error", err.Error())) + } else { + logger.Errorw("[ResetTraffic] Non-retryable error, not retrying", + logger.Field("error", err.Error()), + logger.Field("retryCount", retryCount)) + } + // Reset retry count for next scheduled task + l.clearRetryCount(ctx) + } + } else { + // Task completed successfully, reset retry count + l.clearRetryCount(ctx) + logger.Infow("[ResetTraffic] Task completed successfully", + logger.Field("processingTime", time.Since(startTime)), + logger.Field("retryCount", retryCount)) + } + }() + + // Load last reset time from cache + var cache resetTrafficCache + cacheData, err := l.svc.Redis.Get(ctx, cacheKey).Result() + if err != nil { + if !errors.Is(err, redis.Nil) { + logger.Errorw("[ResetTraffic] Failed to get cache", logger.Field("error", err.Error())) + } + // Set default value if cache not found + cache = resetTrafficCache{ + LastResetTime: time.Now().Add(-10 * time.Minute), + } + logger.Infow("[ResetTraffic] Using default cache value", logger.Field("lastResetTime", cache.LastResetTime)) + } else { + // Parse JSON data + if err := json.Unmarshal([]byte(cacheData), &cache); err != nil { + logger.Errorw("[ResetTraffic] Failed to unmarshal cache", logger.Field("error", err.Error())) + cache = resetTrafficCache{ + LastResetTime: time.Now().Add(-10 * time.Minute), + } + } else { + logger.Infow("[ResetTraffic] Cache loaded successfully", logger.Field("lastResetTime", cache.LastResetTime)) + } + } + + // Execute reset operations in order: yearly -> monthly (1st) -> monthly (cycle) + err = l.resetYear(ctx) + if err != nil { + logger.Errorw("[ResetTraffic] Yearly reset failed", logger.Field("error", err.Error())) + return err + } + + err = l.reset1st(ctx, cache) + if err != nil { + logger.Errorw("[ResetTraffic] Monthly 1st reset failed", logger.Field("error", err.Error())) + return err + } + + err = l.resetMonth(ctx) + if err != nil { + logger.Errorw("[ResetTraffic] Monthly cycle reset failed", logger.Field("error", err.Error())) + return err + } + + // Update cache with current time after successful processing + updatedCache := resetTrafficCache{ + LastResetTime: startTime, + } + cacheDataBytes, marshalErr := json.Marshal(updatedCache) + if marshalErr != nil { + logger.Errorw("[ResetTraffic] Failed to marshal cache", logger.Field("error", marshalErr.Error())) + } else { + cacheErr := l.svc.Redis.Set(ctx, cacheKey, cacheDataBytes, 0).Err() + if cacheErr != nil { + logger.Errorw("[ResetTraffic] Failed to update cache", logger.Field("error", cacheErr.Error())) + // Don't return error here as the main task completed successfully + } else { + logger.Infow("[ResetTraffic] Cache updated successfully", logger.Field("newLastResetTime", startTime)) + } + } + + return nil +} + +// resetMonth handles monthly cycle reset based on subscription start date +// reset_cycle = 2: Reset monthly based on subscription start date +func (l *ResetTrafficLogic) resetMonth(ctx context.Context) error { + now := time.Now() + + err := l.svc.UserModel.Transaction(ctx, func(db *gorm.DB) error { + // Get all subscriptions that reset monthly based on start date + var resetMonthSubIds []int64 + err := db.Model(&subscribe.Subscribe{}).Select("`id`").Where("`reset_cycle` = ?", 2).Find(&resetMonthSubIds).Error + if err != nil { + logger.Errorw("[ResetTraffic] Failed to query monthly subscriptions", logger.Field("error", err.Error())) + return err + } + + if len(resetMonthSubIds) == 0 { + logger.Infow("[ResetTraffic] No monthly cycle subscriptions found") + return nil + } + + // Query users for monthly reset based on subscription start date cycle + var monthlyResetUsers []int64 + + // Check if today is the last day of current month + isLastDayOfMonth := now.AddDate(0, 0, 1).Month() != now.Month() + + query := db.Model(&user.Subscribe{}).Select("`id`"). + Where("`subscribe_id` IN ?", resetMonthSubIds). + Where("`status` IN ?", []int64{1, 2}). // Only active subscriptions + Where("TIMESTAMPDIFF(MONTH, CURDATE(),DATE(expire_time)) >= 1") // At least 1 month passed + + if isLastDayOfMonth { + // Last day of month: handle subscription start dates >= today + query = query.Where("DAY(`expire_time`) >= ?", now.Day()) + } else { + // Normal case: exact day match + query = query.Where("DAY(`expire_time`) = ?", now.Day()) + } + + err = query.Find(&monthlyResetUsers).Error + if err != nil { + logger.Errorw("[ResetTraffic] Failed to query monthly reset users", logger.Field("error", err.Error())) + return err + } + + if len(monthlyResetUsers) > 0 { + logger.Infow("[ResetTraffic] Found users for monthly reset", + logger.Field("count", len(monthlyResetUsers)), + logger.Field("userIds", monthlyResetUsers)) + + err = db.Model(&user.Subscribe{}).Where("`id` IN ?", monthlyResetUsers). + Updates(map[string]interface{}{ + "upload": 0, + "download": 0, + "status": 1, // Ensure status is active + "finished_at": nil, + }).Error + if err != nil { + logger.Errorw("[ResetTraffic] Failed to update monthly reset users", logger.Field("error", err.Error())) + return err + } + // Find user subscriptions for these users + var userSubs []*user.Subscribe + err = db.Model(&user.Subscribe{}).Where("`id` IN ?", monthlyResetUsers).Find(&userSubs).Error + if err != nil { + logger.Errorw("[ResetTraffic] Failed to find user subscriptions for 1st reset", logger.Field("error", err.Error())) + return err + } + // Clear cache for these subscriptions + l.clearCache(ctx, userSubs) + logger.Infow("[ResetTraffic] Monthly reset completed", logger.Field("count", len(monthlyResetUsers))) + } else { + logger.Infow("[ResetTraffic] No users found for monthly reset") + } + return l.svc.SubscribeModel.ClearCache(ctx, resetMonthSubIds...) + }) + if err != nil { + logger.Errorw("[ResetTraffic] Monthly reset transaction failed", logger.Field("error", err.Error())) + return err + } + + logger.Infow("[ResetTraffic] Monthly reset process completed") + return nil +} + +// reset1st handles reset on 1st of every month +// reset_cycle = 1: Reset on 1st of every month +func (l *ResetTrafficLogic) reset1st(ctx context.Context, cache resetTrafficCache) error { + now := time.Now() + + // Check if we already reset this month using cache + if cache.LastResetTime.Year() == now.Year() && cache.LastResetTime.Month() == now.Month() { + logger.Infow("[ResetTraffic] Already reset this month, skipping 1st reset", + logger.Field("lastResetTime", cache.LastResetTime), + logger.Field("currentTime", now)) + return nil + } + + // Only reset if it's the 1st day of the month + if now.Day() != 1 { + logger.Infow("[ResetTraffic] Not 1st day of month, skipping 1st reset", logger.Field("currentDay", now.Day())) + return nil + } + + err := l.svc.UserModel.Transaction(ctx, func(db *gorm.DB) error { + // Get all subscriptions that reset on 1st of month + var reset1stSubIds []int64 + err := db.Model(&subscribe.Subscribe{}).Select("`id`").Where("`reset_cycle` = ?", 1).Find(&reset1stSubIds).Error + if err != nil { + logger.Errorw("[ResetTraffic] Failed to query 1st reset subscriptions", logger.Field("error", err.Error())) + return err + } + + if len(reset1stSubIds) == 0 { + logger.Infow("[ResetTraffic] No 1st reset subscriptions found") + return nil + } + + // Get all active users with these subscriptions + var users1stReset []int64 + err = db.Model(&user.Subscribe{}).Select("`id`"). + Where("`subscribe_id` IN ?", reset1stSubIds). + Where("`status` IN ?", []int64{1, 2}). // Only active subscriptions + Find(&users1stReset).Error + if err != nil { + logger.Errorw("[ResetTraffic] Failed to query 1st reset users", logger.Field("error", err.Error())) + return err + } + + if len(users1stReset) > 0 { + logger.Infow("[ResetTraffic] Found users for 1st reset", + logger.Field("count", len(users1stReset)), + logger.Field("userIds", users1stReset)) + + // Reset upload and download traffic to zero + err = db.Model(&user.Subscribe{}).Where("`id` IN ?", users1stReset). + Updates(map[string]interface{}{ + "upload": 0, + "download": 0, + "status": 1, // Ensure status is active + "finished_at": nil, + }).Error + if err != nil { + logger.Errorw("[ResetTraffic] Failed to update 1st reset users", logger.Field("error", err.Error())) + return err + } + var userSubs []*user.Subscribe + err = db.Model(&user.Subscribe{}).Where("`id` IN ?", users1stReset).Find(&userSubs).Error + if err != nil { + logger.Errorw("[ResetTraffic] Failed to find user subscriptions for 1st reset", logger.Field("error", err.Error())) + return err + } + + // Clear cache for these subscriptions + l.clearCache(ctx, userSubs) + logger.Infow("[ResetTraffic] 1st reset completed", logger.Field("count", len(users1stReset))) + } else { + logger.Infow("[ResetTraffic] No users found for 1st reset") + } + + return l.svc.SubscribeModel.ClearCache(ctx, reset1stSubIds...) + }) + + if err != nil { + logger.Errorw("[ResetTraffic] 1st reset transaction failed", logger.Field("error", err.Error())) + return err + } + logger.Infow("[ResetTraffic] 1st reset process completed") + return nil +} + +// resetYear handles yearly reset based on subscription start date anniversary +// reset_cycle = 3: Reset yearly based on subscription start date +func (l *ResetTrafficLogic) resetYear(ctx context.Context) error { + now := time.Now() + + err := l.svc.UserModel.Transaction(ctx, func(db *gorm.DB) error { + // Get all subscriptions that reset yearly + var resetYearSubIds []int64 + err := db.Model(&subscribe.Subscribe{}).Select("`id`").Where("`reset_cycle` = ?", 3).Find(&resetYearSubIds).Error + if err != nil { + logger.Errorw("[ResetTraffic] Failed to query yearly subscriptions", logger.Field("error", err.Error())) + return err + } + + if len(resetYearSubIds) == 0 { + logger.Infow("[ResetTraffic] No yearly reset subscriptions found") + return nil + } + + // Query users for yearly reset based on subscription start date anniversary + var usersYearReset []int64 + + // Check if today is February 28th (handle leap year case) + isLeapYearCase := now.Month() == 2 && now.Day() == 28 + + query := db.Model(&user.Subscribe{}).Select("`id`"). + Where("`subscribe_id` IN ?", resetYearSubIds). + Where("MONTH(expire_time) = ?", now.Month()). // Same month + Where("`status` IN ?", []int64{1, 2}). // Only active subscriptions + Where("TIMESTAMPDIFF(YEAR, CURDATE(),DATE(expire_time)) >= 1") // At least 1 year passed + if isLeapYearCase { + // February 28th: handle both Feb 28 and Feb 29 subscriptions + query = query.Where("DAY(expire_time) IN (28, 29)") + } else { + // Normal case: exact day match + query = query.Where("DAY(expire_time) = ?", now.Day()) + } + + err = query.Find(&usersYearReset).Error + if err != nil { + logger.Errorw("[ResetTraffic] Query yearly reset users failed", logger.Field("error", err.Error())) + return err + } + + if len(usersYearReset) > 0 { + logger.Infow("[ResetTraffic] Found users for yearly reset", + logger.Field("count", len(usersYearReset)), + logger.Field("userIds", usersYearReset)) + + // Reset upload and download traffic to zero + err = db.Model(&user.Subscribe{}).Where("`id` IN ?", usersYearReset). + Updates(map[string]interface{}{ + "upload": 0, + "download": 0, + "status": 1, // Ensure status is active + "finished_at": nil, + }).Error + if err != nil { + logger.Errorw("[ResetTraffic] Failed to update yearly reset users", logger.Field("error", err.Error())) + return err + } + // Find user subscriptions for these users + var userSubs []*user.Subscribe + err = db.Model(&user.Subscribe{}).Where("`id` IN ?", usersYearReset).Find(&userSubs).Error + if err != nil { + logger.Errorw("[ResetTraffic] Failed to find user subscriptions for 1st reset", logger.Field("error", err.Error())) + return err + } + // Clear cache for these subscriptions + l.clearCache(ctx, userSubs) + logger.Infow("[ResetTraffic] Yearly reset completed", logger.Field("count", len(usersYearReset))) + } else { + logger.Infow("[ResetTraffic] No users found for yearly reset") + } + err = l.svc.SubscribeModel.ClearCache(ctx, resetYearSubIds...) + if err != nil { + logger.Errorw("[ResetTraffic] Failed to clear yearly reset subscription cache", logger.Field("error", err.Error())) + } + return nil + }) + + if err != nil { + logger.Errorw("[ResetTraffic] Yearly reset transaction failed", logger.Field("error", err.Error())) + return err + } + + logger.Infow("[ResetTraffic] Yearly reset process completed") + return nil +} + +// getRetryCount retrieves the current retry count from Redis +func (l *ResetTrafficLogic) getRetryCount(ctx context.Context) int { + countStr, err := l.svc.Redis.Get(ctx, retryCountKey).Result() + if err != nil { + if errors.Is(err, redis.Nil) { + return 0 // No retry count found, start with 0 + } + logger.Errorw("[ResetTraffic] Failed to get retry count", logger.Field("error", err.Error())) + return 0 + } + + count, err := strconv.Atoi(countStr) + if err != nil { + logger.Errorw("[ResetTraffic] Invalid retry count format", logger.Field("value", countStr)) + return 0 + } + + return count +} + +// setRetryCount sets the retry count in Redis +func (l *ResetTrafficLogic) setRetryCount(ctx context.Context, count int) { + err := l.svc.Redis.Set(ctx, retryCountKey, count, 24*time.Hour).Err() + if err != nil { + logger.Errorw("[ResetTraffic] Failed to set retry count", + logger.Field("count", count), + logger.Field("error", err.Error())) + } +} + +// clearRetryCount removes the retry count from Redis +func (l *ResetTrafficLogic) clearRetryCount(ctx context.Context) { + err := l.svc.Redis.Del(ctx, retryCountKey).Err() + if err != nil { + logger.Errorw("[ResetTraffic] Failed to clear retry count", logger.Field("error", err.Error())) + } +} + +// acquireLock attempts to acquire a distributed lock +func (l *ResetTrafficLogic) acquireLock(ctx context.Context) bool { + result := l.svc.Redis.SetNX(ctx, lockKey, "locked", lockTimeout) + acquired, err := result.Result() + if err != nil { + logger.Errorw("[ResetTraffic] Failed to acquire lock", logger.Field("error", err.Error())) + return false + } + + if acquired { + logger.Infow("[ResetTraffic] Lock acquired successfully") + } else { + logger.Infow("[ResetTraffic] Lock already exists, another task is running") + } + + return acquired +} + +// releaseLock releases the distributed lock +func (l *ResetTrafficLogic) releaseLock(ctx context.Context) { + err := l.svc.Redis.Del(ctx, lockKey).Err() + if err != nil { + logger.Errorw("[ResetTraffic] Failed to release lock", logger.Field("error", err.Error())) + } else { + logger.Infow("[ResetTraffic] Lock released successfully") + } +} + +// isRetryableError determines if an error is retryable +func (l *ResetTrafficLogic) isRetryableError(err error) bool { + if err == nil { + return false + } + + errorMessage := strings.ToLower(err.Error()) + + // Network and connection errors (retryable) + retryableErrors := []string{ + "connection refused", + "connection reset", + "connection timeout", + "network", + "timeout", + "dial", + "context deadline exceeded", + "temporary failure", + "server error", + "service unavailable", + "internal server error", + "database is locked", + "too many connections", + "deadlock", + "lock wait timeout", + } + + // Database constraint errors (non-retryable) + nonRetryableErrors := []string{ + "foreign key constraint", + "unique constraint", + "check constraint", + "not null constraint", + "invalid input syntax", + "column does not exist", + "table does not exist", + "permission denied", + "access denied", + "authentication failed", + "invalid credentials", + } + + // Check for non-retryable errors first + for _, nonRetryable := range nonRetryableErrors { + if strings.Contains(errorMessage, nonRetryable) { + logger.Infow("[ResetTraffic] Non-retryable error detected", + logger.Field("error", err.Error()), + logger.Field("pattern", nonRetryable)) + return false + } + } + + // Check for retryable errors + for _, retryable := range retryableErrors { + if strings.Contains(errorMessage, retryable) { + logger.Infow("[ResetTraffic] Retryable error detected", + logger.Field("error", err.Error()), + logger.Field("pattern", retryable)) + return true + } + } + + // Default: treat unknown errors as retryable, but log for analysis + logger.Infow("[ResetTraffic] Unknown error type, treating as retryable", + logger.Field("error", err.Error())) + return true +} + +// clearCache clears the reset traffic cache +func (l *ResetTrafficLogic) clearCache(ctx context.Context, list []*user.Subscribe) { + if len(list) != 0 { + subs := make(map[int64]bool) + + for _, sub := range list { + if sub.SubscribeId > 0 { + err := l.svc.UserModel.ClearSubscribeCache(ctx, sub) + if err != nil { + logger.Errorw("[ResetTraffic] Failed to clear cache for subscription", + logger.Field("subscribeId", sub.SubscribeId), + logger.Field("error", err.Error())) + } + if _, ok := subs[sub.SubscribeId]; !ok { + subs[sub.SubscribeId] = true + } + } + // Insert traffic reset log + l.insertLog(ctx, sub.Id, sub.UserId) + } + + for sub, _ := range subs { + if err := l.svc.SubscribeModel.ClearCache(ctx, sub); err != nil { + logger.Errorw("[ResetTraffic] Failed to clear subscription cache", + logger.Field("subscribeId", sub), + logger.Field("error", err.Error()), + ) + } + } + } +} + +// insertLog inserts a reset traffic log entry +func (l *ResetTrafficLogic) insertLog(ctx context.Context, subId, userId int64) { + trafficLog := log.ResetSubscribe{ + Type: log.ResetSubscribeTypeAuto, + UserId: userId, + Timestamp: time.Now().UnixMilli(), + } + content, _ := trafficLog.Marshal() + if err := l.svc.DB.WithContext(ctx).Model(&log.SystemLog{}).Create(&log.SystemLog{ + Type: log.TypeResetSubscribe.Uint8(), + ObjectID: subId, + Date: time.Now().Format(time.DateOnly), + Content: string(content), + }).Error; err != nil { + logger.Errorw("[ResetTraffic] Failed to create system log for subscription", logger.Field("error", err.Error())) + } +} diff --git a/queue/logic/traffic/serverDataLogic.go b/queue/logic/traffic/serverDataLogic.go index 83516de..f8ca675 100644 --- a/queue/logic/traffic/serverDataLogic.go +++ b/queue/logic/traffic/serverDataLogic.go @@ -5,12 +5,12 @@ import ( "encoding/json" "time" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/config" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/internal/types" + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" ) type ServerDataLogic struct { @@ -73,7 +73,7 @@ func (l *ServerDataLogic) getRanking(ctx context.Context) (top10ServerToday, top if s.ServerId == 0 { continue } - serverInfo, err := l.svc.ServerModel.FindOne(ctx, s.ServerId) + serverInfo, err := l.svc.NodeModel.FindOneServer(ctx, s.ServerId) if err != nil { logger.Error("[ServerDataLogic] Find server failed", logger.Field("error", err.Error())) continue @@ -92,7 +92,7 @@ func (l *ServerDataLogic) getRanking(ctx context.Context) (top10ServerToday, top logger.Error("[ServerDataLogic] Get top servers traffic by day failed", logger.Field("error", err.Error())) } else { for _, s := range serverYesterday { - serverInfo, err := l.svc.ServerModel.FindOne(ctx, s.ServerId) + serverInfo, err := l.svc.NodeModel.FindOneServer(ctx, s.ServerId) if err != nil { logger.Error("[ServerDataLogic] Find server failed", logger.Field("error", err.Error())) continue diff --git a/queue/logic/traffic/trafficStatLogic.go b/queue/logic/traffic/trafficStatLogic.go new file mode 100644 index 0000000..81a422b --- /dev/null +++ b/queue/logic/traffic/trafficStatLogic.go @@ -0,0 +1,176 @@ +package traffic + +import ( + "context" + "time" + + "github.com/hibiken/asynq" + "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/internal/model/traffic" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/logger" +) + +type StatLogic struct { + svc *svc.ServiceContext +} + +func NewStatLogic(svc *svc.ServiceContext) *StatLogic { + return &StatLogic{ + svc: svc, + } +} + +func (l *StatLogic) ProcessTask(ctx context.Context, _ *asynq.Task) error { + now := time.Now() + tx := l.svc.DB.Begin() + var err error + defer func(err error) { + if err != nil { + logger.Errorf("[Traffic Stat Queue] Process task failed: %v", err.Error()) + tx.Rollback() + } else { + logger.Infof("[Traffic Stat Queue] Process task completed successfully, consuming: %s", time.Since(now).String()) + // 提交事务 + if err = tx.Commit().Error; err != nil { + logger.Errorf("[Traffic Stat Queue] Commit transaction failed: %v", err.Error()) + } + } + }(err) + + // 获取全部有效订阅 + var userTraffic []log.UserTraffic + // 获取统计时间范围 + start := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local) + end := start.Add(24 * time.Hour).Add(-time.Nanosecond) + + // 查询用户流量统计, 按用户和订阅分组 + err = tx.WithContext(ctx).Model(&traffic.TrafficLog{}). + Select("user_id, subscribe_id, SUM(download + upload) AS total, SUM(download) AS download, SUM(upload) AS upload"). + Where("timestamp BETWEEN ? AND ?", start, end). + Group("user_id, subscribe_id"). + Order("total DESC"). + Scan(&userTraffic).Error + if err != nil { + logger.Errorf("[Traffic Stat Queue] Query user traffic failed: %v", err.Error()) + return err + } + + date := start.Format(time.DateOnly) + + userTop10 := log.UserTrafficRank{ + Rank: make(map[uint8]log.UserTraffic), + } + + // 更新用户流量统计 + for i, trafficData := range userTraffic { + if i < 10 { + userTop10.Rank[uint8(i+1)] = trafficData + } + // 更新用户流量统计日志 + content, _ := trafficData.Marshal() + err = tx.WithContext(ctx).Model(&log.SystemLog{}).Create(&log.SystemLog{ + Type: log.TypeSubscribeTraffic.Uint8(), + Date: date, + ObjectID: trafficData.SubscribeId, + Content: string(content), + }).Error + if err != nil { + logger.Errorf("[Traffic Stat Queue] Create user traffic log failed: %v", err.Error()) + return err + } + } + + userTop10Content, _ := userTop10.Marshal() + + // 更新用户排行榜 + err = tx.WithContext(ctx).Model(&log.SystemLog{}).Create(&log.SystemLog{ + Type: log.TypeUserTrafficRank.Uint8(), + Date: date, + ObjectID: 0, // 0表示全局用户排行榜 + Content: string(userTop10Content), + }).Error + if err != nil { + logger.Errorf("[Traffic Stat Queue] Create user traffic rank log failed: %v", err.Error()) + return err + } + + // 统计服务器流量 + var serverTraffic []log.ServerTraffic + err = tx.WithContext(ctx).Model(&traffic.TrafficLog{}). + Select("server_id, SUM(download + upload) AS total, SUM(download) AS download, SUM(upload) AS upload"). + Where("timestamp BETWEEN ? AND ?", start, end). + Group("server_id"). + Order("total DESC"). + Scan(&serverTraffic).Error + if err != nil { + logger.Errorf("[Traffic Stat Queue] Query server traffic failed: %v", err.Error()) + return err + } + + serverTop10 := log.ServerTrafficRank{ + Rank: make(map[uint8]log.ServerTraffic), + } + for i, trafficData := range serverTraffic { + if i < 10 { + serverTop10.Rank[uint8(i+1)] = trafficData + } + // 更新服务器流量统计日志 + content, _ := trafficData.Marshal() + err = tx.WithContext(ctx).Model(&log.SystemLog{}).Create(&log.SystemLog{ + Type: log.TypeServerTraffic.Uint8(), + Date: date, + ObjectID: trafficData.ServerId, + Content: string(content), + }).Error + if err != nil { + logger.Errorf("[Traffic Stat Queue] Create server traffic log failed: %v", err.Error()) + return err + } + } + serverTop10Content, _ := serverTop10.Marshal() + // 更新服务器排行榜 + err = tx.WithContext(ctx).Model(&log.SystemLog{}).Create(&log.SystemLog{ + Type: log.TypeServerTrafficRank.Uint8(), + Date: date, + ObjectID: 0, // 0表示全局服务器排行榜 + Content: string(serverTop10Content), + }).Error + if err != nil { + logger.Errorf("[Traffic Stat Queue] Create server traffic rank log failed: %v", err.Error()) + return err + } + + // traffic stat + var stat log.TrafficStat + err = tx.WithContext(ctx).Model(&traffic.TrafficLog{}). + Select("SUM(download + upload) AS total, SUM(download) AS download, SUM(upload) AS upload"). + Where("timestamp BETWEEN ? AND ?", start, end). + Scan(&stat).Error + if err != nil { + logger.Errorf("[Traffic Stat Queue] Query traffic stat failed: %v", err.Error()) + return err + } + + // 更新流量统计日志 + content, _ := stat.Marshal() + err = tx.WithContext(ctx).Model(&log.SystemLog{}).Create(&log.SystemLog{ + Type: log.TypeTrafficStat.Uint8(), + Date: date, + ObjectID: 0, + Content: string(content), + }).Error + if err != nil { + logger.Errorf("[Traffic Stat Queue] Create traffic stat log failed: %v", err.Error()) + return err + } + + // Delete old traffic logs + if l.svc.Config.Log.AutoClear { + err = tx.WithContext(ctx).Model(&traffic.TrafficLog{}).Where("created_at <= ?", end.AddDate(0, 0, int(-l.svc.Config.Log.ClearDays))).Delete(&traffic.TrafficLog{}).Error + if err != nil { + logger.Errorf("[Traffic Stat Queue] Delete server traffic log failed: %v", err.Error()) + } + } + return nil +} diff --git a/queue/logic/traffic/trafficStatisticsLogic.go b/queue/logic/traffic/trafficStatisticsLogic.go index d9b3a6d..ed98cd1 100644 --- a/queue/logic/traffic/trafficStatisticsLogic.go +++ b/queue/logic/traffic/trafficStatisticsLogic.go @@ -3,14 +3,16 @@ package traffic import ( "context" "encoding/json" + "strings" "time" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/internal/model/node" + "github.com/perfect-panel/server/pkg/logger" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/model/traffic" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/model/traffic" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/queue/types" ) //goland:noinspection GoNameStartsWithPackageName @@ -38,7 +40,7 @@ func (l *TrafficStatisticsLogic) ProcessTask(ctx context.Context, task *asynq.Ta return nil } // query server info - serverInfo, err := l.svc.ServerModel.FindOne(ctx, payload.ServerId) + serverInfo, err := l.svc.NodeModel.FindOneServer(ctx, payload.ServerId) if err != nil { logger.WithContext(ctx).Error("[TrafficStatistics] Find server info failed", logger.Field("serverId", payload.ServerId), @@ -46,27 +48,38 @@ func (l *TrafficStatisticsLogic) ProcessTask(ctx context.Context, task *asynq.Ta ) return nil } - if serverInfo.TrafficRatio == 0 { - logger.WithContext(ctx).Error("[TrafficStatistics] Server log ratio is 0", - logger.Field("serverId", payload.ServerId), - ) + // query protocol ratio + // default ratio is 1.0 + + protocols, err := serverInfo.UnmarshalProtocols() + if err != nil { + logger.Errorf("[TrafficStatistics] Unmarshal protocols failed: %s", err.Error()) return nil } + var protocol *node.Protocol + + var ratio float32 = 1.0 + + for _, p := range protocols { + if strings.ToLower(p.Type) == strings.ToLower(payload.Protocol) { + protocol = &p + break + } + } + + if protocol == nil { + logger.WithContext(ctx).Error("[TrafficStatistics] Protocol not found: %s", payload.Protocol) + return nil + } + + // use protocol ratio if it's greater than 0 + if protocol.Ratio > 0 { + ratio = float32(protocol.Ratio) + } + now := time.Now() realTimeMultiplier := l.svc.NodeMultiplierManager.GetMultiplier(now) for _, log := range payload.Logs { - // update user subscribe with log - d := int64(float32(log.Download) * serverInfo.TrafficRatio * realTimeMultiplier) - u := int64(float32(log.Upload) * serverInfo.TrafficRatio * realTimeMultiplier) - if err := l.svc.UserModel.UpdateUserSubscribeWithTraffic(ctx, log.SID, d, u); err != nil { - logger.WithContext(ctx).Error("[TrafficStatistics] Update user subscribe with log failed", - logger.Field("sid", log.SID), - logger.Field("download", float32(log.Download)*serverInfo.TrafficRatio), - logger.Field("upload", float32(log.Upload)*serverInfo.TrafficRatio), - logger.Field("error", err.Error()), - ) - continue - } // query user Subscribe Info sub, err := l.svc.UserModel.FindOneSubscribe(ctx, log.SID) if err != nil { @@ -77,8 +90,25 @@ func (l *TrafficStatisticsLogic) ProcessTask(ctx context.Context, task *asynq.Ta continue } + if log.Download+log.Upload <= l.svc.Config.Node.TrafficReportThreshold { + // no traffic, skip + continue + } + // update user subscribe with log + d := int64(float32(log.Download) * ratio * realTimeMultiplier) + u := int64(float32(log.Upload) * ratio * realTimeMultiplier) + if err := l.svc.UserModel.UpdateUserSubscribeWithTraffic(ctx, sub.Id, d, u); err != nil { + logger.WithContext(ctx).Error("[TrafficStatistics] Update user subscribe with log failed", + logger.Field("sid", log.SID), + logger.Field("download", float32(log.Download)*ratio), + logger.Field("upload", float32(log.Upload)*ratio), + logger.Field("error", err.Error()), + ) + continue + } + // create log log - if err := l.svc.TrafficLogModel.Insert(ctx, &traffic.TrafficLog{ + if err = l.svc.TrafficLogModel.Insert(ctx, &traffic.TrafficLog{ ServerId: payload.ServerId, SubscribeId: log.SID, UserId: sub.UserId, @@ -88,8 +118,8 @@ func (l *TrafficStatisticsLogic) ProcessTask(ctx context.Context, task *asynq.Ta }); err != nil { logger.WithContext(ctx).Error("[TrafficStatistics] Create log log failed", logger.Field("uid", log.SID), - logger.Field("download", float32(log.Download)*serverInfo.TrafficRatio), - logger.Field("upload", float32(log.Upload)*serverInfo.TrafficRatio), + logger.Field("download", float32(log.Download)*ratio), + logger.Field("upload", float32(log.Upload)*ratio), logger.Field("error", err.Error()), ) } diff --git a/queue/queue.go b/queue/queue.go index 6384412..abc5478 100644 --- a/queue/queue.go +++ b/queue/queue.go @@ -2,9 +2,9 @@ package queue import ( "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/pkg/logger" - "github.com/perfect-panel/ppanel-server/queue/handler" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/queue/handler" ) type Service struct { diff --git a/queue/types/email.go b/queue/types/email.go index 2cbdf2e..4fee979 100644 --- a/queue/types/email.go +++ b/queue/types/email.go @@ -5,10 +5,19 @@ const ( ForthwithSendEmail = "forthwith:email:send" ) +const ( + EmailTypeVerify = "verify" + EmailTypeMaintenance = "maintenance" + EmailTypeExpiration = "expiration" + EmailTypeTrafficExceed = "traffic_exceed" + EmailTypeCustom = "custom" +) + type ( SendEmailPayload struct { - Email string `json:"to"` - Subject string `json:"subject"` - Content string `json:"content"` + Type string `json:"type"` + Email string `json:"to"` + Subject string `json:"subject"` + Content map[string]interface{} `json:"content"` } ) diff --git a/queue/types/order.go b/queue/types/order.go index 5d05f9f..f35c570 100644 --- a/queue/types/order.go +++ b/queue/types/order.go @@ -6,9 +6,6 @@ const ( ) type ( - DeferCheckOrderLogic struct { - OrderNo string `json:"order_no"` - } DeferCloseOrderPayload struct { OrderNo string `json:"order_no"` } diff --git a/queue/types/scheduler.go b/queue/types/scheduler.go index 13b3028..51ef48c 100644 --- a/queue/types/scheduler.go +++ b/queue/types/scheduler.go @@ -3,5 +3,6 @@ package types const ( SchedulerCheckSubscription = "scheduler:check:subscription" SchedulerTotalServerData = "scheduler:total:server" - SchedulerCheckOrder = "scheduler:check:order" + SchedulerResetTraffic = "scheduler:reset:traffic" + SchedulerTrafficStat = "scheduler:traffic:stat" ) diff --git a/queue/types/server.go b/queue/types/server.go index 75ec89c..3202ed1 100644 --- a/queue/types/server.go +++ b/queue/types/server.go @@ -10,6 +10,7 @@ type UserTraffic struct { type TrafficStatistics struct { ServerId int64 `json:"server_id"` + Protocol string `json:"protocol"` Logs []UserTraffic `json:"logs"` } diff --git a/queue/types/sms.go b/queue/types/sms.go index 8d9f9e7..affb521 100644 --- a/queue/types/sms.go +++ b/queue/types/sms.go @@ -1,7 +1,7 @@ package types const ( - // ForthwithSendEmail forthwith send email + // ForthwithSendSms forthwith send email ForthwithSendSms = "forthwith:sms:send" ) diff --git a/queue/types/task.go b/queue/types/task.go new file mode 100644 index 0000000..0115f28 --- /dev/null +++ b/queue/types/task.go @@ -0,0 +1,9 @@ +package types + +const ( + // ScheduledBatchSendEmail scheduled batch send email + ScheduledBatchSendEmail = "scheduled:email:batch" + + // ForthwithQuotaTask create quota task immediately + ForthwithQuotaTask = "forthwith:quota:task" +) diff --git a/readme.md b/readme.md index e60f85a..975f5a7 100644 --- a/readme.md +++ b/readme.md @@ -1,71 +1,291 @@ -## Directory Structure +# PPanel Server -```text +
+ +[![License](https://img.shields.io/github/license/perfect-panel/server)](LICENSE) +[![Go Version](https://img.shields.io/badge/Go-1.21%2B-blue)](https://go.dev/) +[![Go Report Card](https://goreportcard.com/badge/github.com/perfect-panel/server)](https://goreportcard.com/report/github.com/perfect-panel/server) +[![Docker](https://img.shields.io/badge/Docker-Available-blue)](Dockerfile) +[![CI/CD](https://img.shields.io/github/actions/workflow/status/perfect-panel/server/release.yml)](.github/workflows/release.yml) + +**PPanel is a pure, professional, and perfect open-source proxy panel tool, designed for learning and practical use.** + +[English](README.md) | [中文](readme_zh.md) | [Report Bug](https://github.com/perfect-panel/server/issues/new) | [Request Feature](https://github.com/perfect-panel/server/issues/new) + +
+ +> **Article 1.** +> All human beings are born free and equal in dignity and rights. +> They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood. +> +> **Article 12.** +> No one shall be subjected to arbitrary interference with his privacy, family, home or correspondence, nor to attacks upon his honour and reputation. +> Everyone has the right to the protection of the law against such interference or attacks. +> +> **Article 19.** +> Everyone has the right to freedom of opinion and expression; this right includes freedom to hold opinions without interference and to seek, receive and impart information and ideas through any media and regardless of frontiers. +> +> *Source: [United Nations – Universal Declaration of Human Rights (UN.org)](https://www.un.org/sites/un2.un.org/files/2021/03/udhr.pdf)* + +## 📋 Overview + +PPanel Server is the backend component of the PPanel project, providing robust APIs and core functionality for managing +proxy services. Built with Go, it emphasizes performance, security, and scalability. + +### Key Features + +- **Multi-Protocol Support**: Supports Shadowsocks, V2Ray, Trojan, and more. +- **Privacy First**: No user logs are collected, ensuring privacy and security. +- **Minimalist Design**: Simple yet powerful, with complete business logic. +- **User Management**: Full authentication and authorization system. +- **Subscription System**: Manage user subscriptions and service provisioning. +- **Payment Integration**: Supports multiple payment gateways. +- **Order Management**: Track and process user orders. +- **Ticket System**: Built-in customer support and issue tracking. +- **Node Management**: Monitor and control server nodes. +- **API Framework**: Comprehensive RESTful APIs for frontend integration. + +## 🚀 Quick Start + +### Prerequisites + +- **Go**: 1.21 or higher +- **Docker**: Optional, for containerized deployment +- **Git**: For cloning the repository + +### Installation from Source + +1. **Clone the repository**: + ```bash + git clone https://github.com/perfect-panel/ppanel-server.git + cd ppanel-server + ``` + +2. **Install dependencies**: + ```bash + go mod download + ``` + +3. **Generate code**: + ```bash + chmod +x script/generate.sh + ./script/generate.sh + ``` + +4. **Build the project**: + ```bash + make linux-amd64 + ``` + +5. **Run the server**: + ```bash + ./ppanel-server-linux-amd64 run --config etc/ppanel.yaml + ``` + +### 🐳 Docker Deployment + +1. **Build the Docker image**: + ```bash + docker buildx build --platform linux/amd64 -t ppanel-server:latest . + ``` + +2. **Run the container**: + ```bash + docker run --rm -p 8080:8080 -v $(pwd)/etc:/app/etc ppanel-server:latest + ``` + +3. **Use Docker Compose** (create `docker-compose.yml`): + ```yaml + version: '3.8' + services: + ppanel-server: + image: ppanel-server:latest + ports: + - "8080:8080" + volumes: + - ./etc:/app/etc + environment: + - TZ=Asia/Shanghai + ``` + Run: + ```bash + docker-compose up -d + ``` + +4. **Pull from Docker Hub** (after CI/CD publishes): + ```bash + docker pull ppanel/ppanel-server:latest + docker run --rm -p 8080:8080 ppanel/ppanel-server:latest + ``` + +## 📖 API Documentation + +Explore the full API documentation: + +- **Swagger**: [https://ppanel.dev/en-US/swagger/ppanel](https://ppanel.dev/swagger/ppanel) + +The documentation covers all endpoints, request/response formats, and authentication details. + +## 🔗 Related Projects + +| Project | Description | Link | +|------------------|----------------------------|-------------------------------------------------------| +| PPanel Web | Frontend for PPanel | [GitHub](https://github.com/perfect-panel/ppanel-web) | +| PPanel User Web | User interface for PPanel | [Preview](https://user.ppanel.dev) | +| PPanel Admin Web | Admin interface for PPanel | [Preview](https://admin.ppanel.dev) | + +## 🌐 Official Website + +Visit [ppanel.dev](https://ppanel.dev/) for more details. + +## 🏛 Architecture + +![Architecture Diagram](./doc/image/architecture-en.png) + +## 📁 Directory Structure + +``` . -├── etc -├── cmd -├── queue -├── generate -├── initialize -├── go.mod -├── internal -│ ├── config -│ ├── handler -│ ├── middleware -│ ├── logic -│ ├── svc -│ ├── types -│ └── model -├── scheduler -├── pkg -└── script -``` -- apis: API definition files -- etc: Directory for static configuration files -- cmd:Application entry point -- queue:Queue consumption service -- generate:Code generation tools -- initialize: Initialization system configuration -- internal:Internal modules - - config:Configuration file parsing - - handler:HTTP interface handling, with `handler` as the fixed suffix - - middleware:HTTP middleware - - logic:Business logic handling, with `logic` as the fixed suffix - - svc:Service layer encapsulation - - types:Type definitions - - model:Data models -- scheduler:Scheduled tasks -- pkg: Common utility code -- script:Build scripts - - -##### Generate Code - -```bash -$ chmod +x script/generate.sh -$ ./script/generate.sh +├── apis/ # API definition files +├── cmd/ # Application entry point +├── doc/ # Documentation +├── etc/ # Configuration files (e.g., ppanel.yaml) +├── generate/ # Code generation tools +├── initialize/ # System initialization +├── internal/ # Internal modules +│ ├── config/ # Configuration parsing +│ ├── handler/ # HTTP handlers +│ ├── middleware/ # HTTP middleware +│ ├── logic/ # Business logic +│ ├── model/ # Data models +│ ├── svc/ # Service layer +│ └── types/ # Type definitions +├── pkg/ # Utility code +├── queue/ # Queue services +├── scheduler/ # Scheduled tasks +├── script/ # Build scripts +├── go.mod # Go module definition +├── Makefile # Build automation +└── Dockerfile # Docker configuration ``` -##### Generate Swagger +## 💻 Development +### Format API Files ```bash -$ goctl api plugin -plugin goctl-swagger='swagger -filename ppanel.json -pack Response -response "[{\"name\":\"code\",\"type\":\"integer\",\"description\":\"状态码\"},{\"name\":\"msg\",\"type\":\"string\",\"description\":\"消息\"},{\"name\":\"data\",\"type\":\"object\",\"description\":\"数据\",\"is_data\":true}]";' -api ppanel.api -dir . +goctl api format --dir apis/user.api ``` -##### Format API File +### Add a New API + +1. Create a new API file in `apis/`. +2. Import it in `apis/ppanel.api`. +3. Regenerate code: + ```bash + ./script/generate.sh + ``` + +### Build for Multiple Platforms + +Use the `Makefile` to build for various platforms (e.g., Linux, Windows, macOS): ```bash -$ goctl api format --dir api/user.api +make all # Builds linux-amd64, darwin-amd64, windows-amd64 +make linux-arm64 # Build for specific platform ``` -##### Build +Supported platforms include: -```bash -$ go build -o ppanel ppanel.go -``` +- Linux: `386`, `amd64`, `arm64`, `armv5-v7`, `mips`, `riscv64`, `loong64`, etc. +- Windows: `386`, `amd64`, `arm64`, `armv7` +- macOS: `amd64`, `arm64` +- FreeBSD: `amd64`, `arm64` -##### Run +## 🤝 Contributing -```bash -$ ./ppanel run --config etc/ppanel.yaml -``` \ No newline at end of file +Contributions are welcome! Please follow the [Contribution Guidelines](CONTRIBUTING.md) for bug fixes, features, or +documentation improvements. + +## ✨ Special Thanks + +A huge thank you to the following outstanding open-source projects that have provided invaluable support for this +project's development! 🚀 + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
ProjectDescriptionProjectDescription
+ + Gin
+ Gin
+ Gin Stars +
+
+ High-performance Go Web framework
+
+ + Gorm
+ Gorm
+ Gorm Stars +
+
+ Powerful Go ORM framework
+
+ + Asynq
+ Asynq
+ Asynq Stars +
+
+ Asynchronous task queue for Go
+
+ + Go-Swagger
+ Go-Swagger
+ Go-Swagger Stars +
+
+ Comprehensive Go Swagger toolkit
+
+ + Go-Zero
+ Go-Zero
+ Go-Zero Stars +
+
+ Go microservices framework (this project's API generator is built on Go-Zero)
+
+
+ +--- + +🎉 **Salute to Open Source**: Thank you to the open-source community for making development simpler and more efficient! +Please give these projects a ⭐ to support the open-source movement! +## 📄 License + +This project is licensed under the [GPL-3.0 License](LICENSE). \ No newline at end of file diff --git a/readme_zh.md b/readme_zh.md new file mode 100644 index 0000000..ee4a1c4 --- /dev/null +++ b/readme_zh.md @@ -0,0 +1,288 @@ +# PPanel 服务端 + +
+ +[![License](https://img.shields.io/github/license/perfect-panel/server)](LICENSE) +[![Go Version](https://img.shields.io/badge/Go-1.21%2B-blue)](https://go.dev/) +[![Go Report Card](https://goreportcard.com/badge/github.com/perfect-panel/server)](https://goreportcard.com/report/github.com/perfect-panel/server) +[![Docker](https://img.shields.io/badge/Docker-Available-blue)](Dockerfile) +[![CI/CD](https://img.shields.io/github/actions/workflow/status/perfect-panel/server/release.yml)](.github/workflows/release.yml) + +**PPanel 是一个纯净、专业、完美的开源代理面板工具,旨在成为您学习和实际使用的理想选择。** + +[English](README.md) | [中文](readme_zh.md) | [报告问题](https://github.com/perfect-panel/server/issues/new) | [功能请求](https://github.com/perfect-panel/server/issues/new) + +
+ +> **第一条** +> 人人生而自由,在尊严与权利上一律平等。 +> 他们赋有理性与良知,应当以兄弟般的精神彼此相待。 +> +> **第十二条** +> 任何人的隐私、家庭、住宅和通信不得任意干涉,其名誉与荣誉不得加以攻击。 +> 人人有权受到法律的保护,以免遭受这种干涉或攻击。 +> +> **第十九条** +> 人人有思想与表达的自由;此项自由包括持有主张而不受干预,以及通过任何媒介、无论国界,自由寻求、接受和传播信息与思想。 +> +> *来源: [United Nations – Universal Declaration of Human Rights (UN.org)](https://www.un.org/sites/un2.un.org/files/2021/03/udhr.pdf)* + +## 📋 概述 + +PPanel 服务端是 PPanel 项目的后端组件,为代理服务提供强大的 API 和核心功能。它基于 Go 语言开发,注重性能、安全性和可扩展性。 + +### 核心特性 + +- **多协议支持**:支持 Shadowsocks、V2Ray、Trojan 等多种加密协议。 +- **隐私保护**:不收集用户日志,确保隐私和安全。 +- **极简设计**:简单易用,保留完整的业务逻辑。 +- **用户管理**:完善的认证和授权系统。 +- **订阅管理**:处理用户订阅和服务开通。 +- **支付集成**:支持多种支付网关。 +- **订单管理**:跟踪和处理用户订单。 +- **工单系统**:内置客户支持和问题跟踪。 +- **节点管理**:监控和控制服务器节点。 +- **API 框架**:提供全面的 RESTful API,供前端集成。 + +## 🚀 快速开始 + +### 前提条件 + +- **Go**:1.21 或更高版本 +- **Docker**:可选,用于容器化部署 +- **Git**:用于克隆仓库 + +### 通过源代码运行 + +1. **克隆仓库**: + ```bash + git clone https://github.com/perfect-panel/ppanel-server.git + cd ppanel-server + ``` + +2. **安装依赖**: + ```bash + go mod download + ``` + +3. **生成代码**: + ```bash + chmod +x script/generate.sh + ./script/generate.sh + ``` + +4. **构建项目**: + ```bash + make linux-amd64 + ``` + +5. **启动服务器**: + ```bash + ./ppanel-server-linux-amd64 run --config etc/ppanel.yaml + ``` + +### 🐳 Docker 部署 + +1. **构建 Docker 镜像**: + ```bash + docker buildx build --platform linux/amd64 -t ppanel-server:latest . + ``` + +2. **运行容器**: + ```bash + docker run --rm -p 8080:8080 -v $(pwd)/etc:/app/etc ppanel-server:latest + ``` + +3. **使用 Docker Compose**(创建 `docker-compose.yml`): + ```yaml + version: '3.8' + services: + ppanel-server: + image: ppanel-server:latest + ports: + - "8080:8080" + volumes: + - ./etc:/app/etc + environment: + - TZ=Asia/Shanghai + ``` + 运行: + ```bash + docker-compose up -d + ``` + +4. **从 Docker Hub 拉取**(CI/CD 发布后): + ```bash + docker pull ppanel/ppanel-server:latest + docker run --rm -p 8080:8080 ppanel/ppanel-server:latest + ``` + +## 📖 API 文档 + +查看完整的 API 文档: + +- **Swagger 文档**:[https://ppanel.dev/zh-CN/swagger/ppanel](https://ppanel.dev/zh-CN/swagger/ppanel) + +文档涵盖所有 API 端点、请求/响应格式及认证要求。 + +## 🔗 相关项目 + +| 项目 | 描述 | 链接 | +|------------------|--------------|-------------------------------------------------------| +| PPanel Web | PPanel 前端应用 | [GitHub](https://github.com/perfect-panel/ppanel-web) | +| PPanel User Web | PPanel 用户界面 | [预览](https://user.ppanel.dev) | +| PPanel Admin Web | PPanel 管理员界面 | [预览](https://admin.ppanel.dev) | + +## 🌐 官方网站 + +访问 [ppanel.dev](https://ppanel.dev) 获取更多信息。 + +## 🏛 系统架构 + +![Architecture Diagram](./doc/image/architecture-zh.png) + +## 📁 目录结构 + +``` +. +├── apis/ # API 定义文件 +├── cmd/ # 应用程序入口 +├── doc/ # 文档 +├── etc/ # 配置文件(如 ppanel.yaml) +├── generate/ # 代码生成工具 +├── initialize/ # 系统初始化 +├── internal/ # 内部模块 +│ ├── config/ # 配置文件解析 +│ ├── handler/ # HTTP 处理程序 +│ ├── middleware/ # HTTP 中间件 +│ ├── logic/ # 业务逻辑 +│ ├── model/ # 数据模型 +│ ├── svc/ # 服务层 +│ └── types/ # 类型定义 +├── pkg/ # 公共工具代码 +├── queue/ # 队列服务 +├── scheduler/ # 定时任务 +├── script/ # 构建脚本 +├── go.mod # Go 模块定义 +├── Makefile # 构建自动化 +└── Dockerfile # Docker 配置 +``` + +## 💻 开发 + +### 格式化 API 文件 +```bash +goctl api format --dir apis/user.api +``` + +### 添加新 API + +1. 在 `apis/` 目录创建新的 API 文件。 +2. 在 `apis/ppanel.api` 中导入新 API。 +3. 重新生成代码: + ```bash + ./script/generate.sh + ``` + +### 多平台构建 + +使用 `Makefile` 构建多种平台(如 Linux、Windows、macOS): + +```bash +make all # 构建 linux-amd64、darwin-amd64、windows-amd64 +make linux-arm64 # 构建特定平台 +``` + +支持的平台包括: + +- Linux:`386`、`amd64`、`arm64`、`armv5-v7`、`mips`、`riscv64`、`loong64` 等 +- Windows:`386`、`amd64`、`arm64`、`armv7` +- macOS:`amd64`、`arm64` +- FreeBSD:`amd64`、`arm64` + +## 🤝 贡献 + +欢迎各种贡献,包括功能开发、错误修复和文档改进。请查看[贡献指南](CONTRIBUTING_ZH.md)了解详情。 + +## ✨ 特别感谢 + +感谢以下优秀的开源项目,它们为本项目的开发提供了强大的支持! 🚀 + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
项目描述项目描述
+ + Gin
+ Gin
+ Gin Stars +
+
+ 高性能的 Go Web 框架
+
+ + Gorm
+ Gorm
+ Gorm Stars +
+
+ 功能强大的 Go ORM 框架
+
+ + Asynq
+ Asynq
+ Asynq Stars +
+
+ Go 语言的异步任务队列
+
+ + Go-Swagger
+ Go-Swagger
+ Go-Swagger Stars +
+
+ 完整的 Go Swagger 工具集
+
+ + Go-Zero
+ Go-Zero
+ Go-Zero Stars +
+
+ Go 微服务框架(本项目的 API 生成器,基于 Go-Zero 实现)
+
+
+ +--- + +🎉 **致敬开源**:感谢开源社区,让开发变得更简单、更高效!欢迎为这些项目点亮 ⭐,支持开源事业! + +## 📄 许可证 + +本项目采用 [GPL-3.0 许可证](LICENSE) 授权。 \ No newline at end of file diff --git a/scheduler/scheduler.go b/scheduler/scheduler.go index fa6b6f8..377dca3 100644 --- a/scheduler/scheduler.go +++ b/scheduler/scheduler.go @@ -3,11 +3,11 @@ package scheduler import ( "time" - "github.com/perfect-panel/ppanel-server/pkg/logger" + "github.com/perfect-panel/server/pkg/logger" "github.com/hibiken/asynq" - "github.com/perfect-panel/ppanel-server/internal/svc" - "github.com/perfect-panel/ppanel-server/queue/types" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/queue/types" ) type Service struct { @@ -29,16 +29,21 @@ func (m *Service) Start() { if _, err := m.server.Register("@every 60s", checkTask); err != nil { logger.Errorf("register check subscription task failed: %s", err.Error()) } - // schedule total server data task: every 5 minutes - totalServerDataTask := asynq.NewTask(types.SchedulerTotalServerData, nil) - if _, err := m.server.Register("@every 180s", totalServerDataTask); err != nil { - logger.Errorf("register total server data task failed: %s", err.Error()) + //// schedule total server data task: every 5 minutes + //totalServerDataTask := asynq.NewTask(types.SchedulerTotalServerData, nil) + //if _, err := m.server.Register("@every 180s", totalServerDataTask); err != nil { + // logger.Errorf("register total server data task failed: %s", err.Error()) + //} + // schedule reset traffic task: every day at 00:30 + resetTrafficTask := asynq.NewTask(types.SchedulerResetTraffic, nil) + if _, err := m.server.Register("30 0 * * *", resetTrafficTask); err != nil { + logger.Errorf("register reset traffic task failed: %s", err.Error()) } - // schedule total server data task: every 5 minutes - checkOrderTask := asynq.NewTask(types.SchedulerCheckOrder, nil) - if _, err := m.server.Register("@every 10s", checkOrderTask); err != nil { - logger.Errorf("register check order task failed: %s", err.Error()) + // schedule traffic stat task: every day at 00:00 + trafficStatTask := asynq.NewTask(types.SchedulerTrafficStat, nil) + if _, err := m.server.Register("0 0 * * *", trafficStatTask, asynq.MaxRetry(3)); err != nil { + logger.Errorf("register traffic stat task failed: %s", err.Error()) } if err := m.server.Run(); err != nil { diff --git a/script/generate.sh b/script/generate.sh old mode 100644 new mode 100755 index ea116d5..26fd79a --- a/script/generate.sh +++ b/script/generate.sh @@ -6,9 +6,13 @@ ARCH_TYPE=$(uname -m) if [[ "$OS_TYPE" == "Linux" ]]; then echo "The current operating system is Linux" if [[ "$ARCH_TYPE" == "x86_64" ]]; then + echo "Format api file" + ./generate/gopure-linux-amd64 api format --dir ./apis echo "Architecture: amd64" ./generate/gopure-linux-amd64 api go -api *.api -dir . -style goZero elif [[ "$ARCH_TYPE" == "aarch64" ]]; then + echo "Format api file" + ./generate/gopure-linux-arm64 api format --dir ./apis echo "Architecture: arm64" ./generate/gopure-linux-arm64 api go -api *.api -dir . -style goZero else @@ -17,9 +21,13 @@ if [[ "$OS_TYPE" == "Linux" ]]; then elif [[ "$OS_TYPE" == "Darwin" ]]; then echo "The current operating system is macOS" if [[ "$ARCH_TYPE" == "x86_64" ]]; then + echo "Format api file" + ./generate/gopure-darwin-amd64 api format --dir ./apis echo "Architecture: amd64" ./generate/gopure-darwin-amd64 api go -api *.api -dir . -style goZero elif [[ "$ARCH_TYPE" == "arm64" ]]; then + echo "Format api file" + ./generate/gopure-darwin-arm64 api format --dir ./apis echo "Architecture: arm64" ./generate/gopure-darwin-arm64 api go -api *.api -dir . -style goZero else @@ -28,9 +36,13 @@ elif [[ "$OS_TYPE" == "Darwin" ]]; then elif [[ "$OS_TYPE" == "CYGWIN"* || "$OS_TYPE" == "MINGW"* ]]; then echo "The current operating system is Windows" if [[ "$ARCH_TYPE" == "x86_64" ]]; then + echo "Format api file" + ./generate/gopure-amd64.exe api format --dir ./apis echo "Architecture: amd64" ./generate/gopure-amd64.exe api go -api *.api -dir . -style goZero elif [[ "$ARCH_TYPE" == "arm64" ]]; then + echo "Format api file" + ./generate/gopure-arm64.exe api format --dir ./apis echo "Architecture: arm64" ./generate/gopure-arm64.exe api go -api *.api -dir . -style goZero else