mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-31 10:14:15 +00:00
fix(clients): seed all clients when settings.clients has string tgId
The ClientsTable seeder unmarshaled each settings.clients entry into model.Client and silently `continue`d on error. Older inbounds wrote tgId as an empty string for every client past the first; that fails to unmarshal into int64, so only the first client per inbound landed in the new clients table. Normalize tgId and the other int64/int fields on the raw map before marshal+unmarshal: parseable strings convert, empty/unparseable ones drop so the field falls back to zero. Also log on the residual unmarshal-failure path so the next regression is visible. Recover already-seeded installs by re-syncing each inbound's clients into the relational tables from MigrationRequirements, so running `x-ui migrate` heals partial seeds.
This commit is contained in:
parent
d7f47d8b6a
commit
3827d7d061
2 changed files with 40 additions and 0 deletions
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -198,6 +199,36 @@ func runSeeders(isUsersEmpty bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// normalizeClientJSONFields coerces loosely-typed numeric fields in a raw
|
||||||
|
// settings.clients entry so json.Unmarshal into model.Client doesn't fail
|
||||||
|
// when older rows wrote tgId/limitIp/totalGB/etc. as strings. Empty strings
|
||||||
|
// drop the key so the field falls back to its zero value.
|
||||||
|
func normalizeClientJSONFields(obj map[string]any) {
|
||||||
|
normalizeInt := func(key string) {
|
||||||
|
raw, exists := obj[key]
|
||||||
|
if !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s, ok := raw.(string)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
trimmed := strings.ReplaceAll(strings.TrimSpace(s), " ", "")
|
||||||
|
if trimmed == "" {
|
||||||
|
delete(obj, key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n, err := strconv.ParseInt(trimmed, 10, 64); err == nil {
|
||||||
|
obj[key] = n
|
||||||
|
} else {
|
||||||
|
delete(obj, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, k := range []string{"tgId", "limitIp", "totalGB", "expiryTime", "reset", "created_at", "updated_at"} {
|
||||||
|
normalizeInt(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func seedClientsFromInboundJSON() error {
|
func seedClientsFromInboundJSON() error {
|
||||||
var inbounds []model.Inbound
|
var inbounds []model.Inbound
|
||||||
if err := db.Find(&inbounds).Error; err != nil {
|
if err := db.Find(&inbounds).Error; err != nil {
|
||||||
|
|
@ -226,12 +257,15 @@ func seedClientsFromInboundJSON() error {
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
normalizeClientJSONFields(obj)
|
||||||
blob, err := json.Marshal(obj)
|
blob, err := json.Marshal(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var c model.Client
|
var c model.Client
|
||||||
if err := json.Unmarshal(blob, &c); err != nil {
|
if err := json.Unmarshal(blob, &c); err != nil {
|
||||||
|
log.Printf("ClientsTable seed: skip client in inbound %d (unmarshal failed): %v; payload=%s",
|
||||||
|
inbound.Id, err, string(blob))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
email := strings.TrimSpace(c.Email)
|
email := strings.TrimSpace(c.Email)
|
||||||
|
|
|
||||||
|
|
@ -2924,6 +2924,12 @@ func (s *InboundService) MigrationRequirements() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Heal clients table for installs where the one-shot seeder
|
||||||
|
// skipped clients due to a tgId-string unmarshal error.
|
||||||
|
if syncErr := s.clientService.SyncInbound(tx, inbounds[inbound_index].Id, modelClients); syncErr != nil {
|
||||||
|
logger.Warning("MigrationRequirements sync clients failed:", syncErr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tx.Save(inbounds)
|
tx.Save(inbounds)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue