mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-14 18:32:15 +03:00
* fix(clients): preserve enable on portable import Stop BulkCreate and orphan ImportClients from forcing enable=true, and restate Enable=false after GORM Create (clients.enable default:true drops the zero value). Interactive Create still defaults new clients to enabled. Fixes #6478. * fix(clients): respect enable=false on node mirror; omit enable defaults true --------- Co-authored-by: mrchatam <287639636+mrchatam@users.noreply.github.com>
117 lines
3.7 KiB
Go
117 lines
3.7 KiB
Go
// Package service implements the panel's business-logic layer.
|
|
//
|
|
// ClientService owns the lifecycle of VPN clients: creation, update, deletion,
|
|
// attach/detach to inbounds, bulk operations, group membership, traffic resets,
|
|
// and the paginated clients listing. Its surface is split across client_*.go
|
|
// files by responsibility (see each file's contents); they all belong to the
|
|
// same package, so the split is purely organizational. ClientService and
|
|
// InboundService are mutually dependent — most ClientService methods take an
|
|
// *InboundService and InboundService embeds a ClientService — which is why the
|
|
// client code lives in package service rather than a sub-package.
|
|
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
|
)
|
|
|
|
type ClientWithAttachments struct {
|
|
model.ClientRecord
|
|
InboundIds []int `json:"inboundIds"`
|
|
Traffic *xray.ClientTraffic `json:"traffic,omitempty"`
|
|
}
|
|
|
|
// MarshalJSON is required because model.ClientRecord defines its own
|
|
// MarshalJSON. Go promotes the embedded method to the outer struct, so without
|
|
// this the encoder would call ClientRecord.MarshalJSON for the whole value and
|
|
// silently drop InboundIds and Traffic from the API response.
|
|
func (c ClientWithAttachments) MarshalJSON() ([]byte, error) {
|
|
rec, err := json.Marshal(c.ClientRecord)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
extras := struct {
|
|
InboundIds []int `json:"inboundIds"`
|
|
Traffic *xray.ClientTraffic `json:"traffic,omitempty"`
|
|
}{InboundIds: c.InboundIds, Traffic: c.Traffic}
|
|
extra, err := json.Marshal(extras)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(rec) < 2 || rec[len(rec)-1] != '}' || len(extra) <= 2 {
|
|
return rec, nil
|
|
}
|
|
const maxMarshalSize = 256 << 20
|
|
if len(rec) > maxMarshalSize || len(extra) > maxMarshalSize {
|
|
return rec, nil
|
|
}
|
|
out := make([]byte, 0, len(rec)+len(extra))
|
|
out = append(out, rec[:len(rec)-1]...)
|
|
if len(rec) > 2 {
|
|
out = append(out, ',')
|
|
}
|
|
out = append(out, extra[1:]...)
|
|
return out, nil
|
|
}
|
|
|
|
type ClientService struct{}
|
|
|
|
// ErrClientNotInInbound is returned (wrapped) when a client cannot be located
|
|
// in an inbound's settings during deletion. Deletion treats it as non-fatal so
|
|
// the operation stays idempotent and tolerant of pre-existing data drift
|
|
// between the clients table and the inbound's settings JSON.
|
|
var ErrClientNotInInbound = errors.New("client not found in inbound")
|
|
|
|
type ClientCreatePayload struct {
|
|
Client model.Client `json:"client"`
|
|
InboundIds []int `json:"inboundIds"`
|
|
LimitHwid int `json:"-"`
|
|
}
|
|
|
|
const sqlInChunk = 400
|
|
|
|
type clientPayloadWithHwid struct {
|
|
model.Client
|
|
LimitHwid int `json:"limitHwid"`
|
|
}
|
|
|
|
func (p *ClientCreatePayload) UnmarshalJSON(data []byte) error {
|
|
var raw struct {
|
|
Client json.RawMessage `json:"client"`
|
|
InboundIds []int `json:"inboundIds"`
|
|
}
|
|
if err := json.Unmarshal(data, &raw); err != nil {
|
|
return err
|
|
}
|
|
var withHwid clientPayloadWithHwid
|
|
if len(raw.Client) > 0 {
|
|
if err := json.Unmarshal(raw.Client, &withHwid); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
p.Client = withHwid.Client
|
|
p.InboundIds = raw.InboundIds
|
|
p.LimitHwid = withHwid.LimitHwid
|
|
// Omit enable → true (legacy API); explicit false is preserved (#6478).
|
|
var keys map[string]json.RawMessage
|
|
if len(raw.Client) > 0 && json.Unmarshal(raw.Client, &keys) == nil {
|
|
if _, ok := keys["enable"]; !ok {
|
|
p.Client.Enable = true
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p ClientCreatePayload) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(struct {
|
|
Client clientPayloadWithHwid `json:"client"`
|
|
InboundIds []int `json:"inboundIds"`
|
|
}{
|
|
Client: clientPayloadWithHwid{Client: p.Client, LimitHwid: p.LimitHwid},
|
|
InboundIds: p.InboundIds,
|
|
})
|
|
}
|