mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-27 07:29:36 +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:
@@ -11,6 +11,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -198,6 +199,36 @@ func runSeeders(isUsersEmpty bool) error {
|
||||
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 {
|
||||
var inbounds []model.Inbound
|
||||
if err := db.Find(&inbounds).Error; err != nil {
|
||||
@@ -226,12 +257,15 @@ func seedClientsFromInboundJSON() error {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
normalizeClientJSONFields(obj)
|
||||
blob, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var c model.Client
|
||||
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
|
||||
}
|
||||
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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user