mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-21 10:27:14 +00:00
fix: refresh stale client_traffics row when an inbound-deleted client's email is reused (#6003)
* fix: refresh stale client_traffics row when an inbound-deleted client's email is reused AddClientStat's OnConflict was DoNothing on email, so once an inbound is deleted (DelInbound only removes the client_inbounds link, matching ClientService.Detach's intentional Detach-then-later-Attach behavior) the orphaned client_traffics row for that email survives untouched. Re-creating a client under the same email on a new inbound silently kept the old enable/expiry_time/reset/total/inbound_id instead of adopting the new client's config. Switch the conflict path to DoUpdates on inbound_id/total/expiry_time/ enable/reset. up/down stay excluded on purpose: every call for an already-attached identity carries the same config values (one call per inbound), so the refresh is a no-op for that legitimate multi-inbound share, while zeroing usage counters on each additional attach would erase real traffic. Fixes #5958 * fix: don't let AddClientStat clobber import's forced-enabled ClientStats rows github-actions[bot] review on #6003 found that AddInbound writes client_traffics twice for the same import payload: first inserting each ClientStats row (DoNothing, with Enable forced true by controller.importInbound), then calling AddClientStat once per Settings-derived client. With AddClientStat's OnConflict now DoUpdates, that second call was unconditionally overwriting enable (and total/expiry_time/reset/inbound_id) with the Settings.clients[].enable value — which still holds whatever the client had at export time, silently undoing the controller's "always import as enabled" behavior for any client disabled at export. Fix: track which emails were already seeded by the ClientStats loop and skip the AddClientStat call for those emails, leaving the import path's forced values as authoritative. Plain (non-import) creates are unaffected since ClientStats is empty there, so every client still goes through AddClientStat's refresh as before. Also updated a stale comment in addClientTraffic that still described AddClientStat as DoNothing. Added TestAddInbound_ImportForcedEnableSurvivesDisabledSettingsClient, which reproduces the exact regression (verified it fails without this fix) and passes with it.
This commit is contained in:
@@ -125,3 +125,37 @@ func TestAddInbound_ImportStatsMissingClientStillGetsTrafficRow(t *testing.T) {
|
||||
t.Fatalf("erin Total = %d, want 2000 (quota taken from client settings)", erin.Total)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAddInbound_ImportForcedEnableSurvivesDisabledSettingsClient covers a
|
||||
// regression the AddClientStat OnConflict fix (#5958) could otherwise
|
||||
// introduce: controller.importInbound forces every ClientStats row to
|
||||
// Enable=true (imports are meant to bring every client back enabled), but
|
||||
// Settings.clients[].enable is untouched and can still say false for a client
|
||||
// that was disabled at export time. AddInbound runs the ClientStats loop
|
||||
// first (plain insert, Enable=true) and then calls AddClientStat once per
|
||||
// Settings-derived client on the same email — that second call must not let
|
||||
// the stale, disabled Settings value win over the forced-enabled row.
|
||||
func TestAddInbound_ImportForcedEnableSurvivesDisabledSettingsClient(t *testing.T) {
|
||||
setupConflictDB(t)
|
||||
svc := &InboundService{}
|
||||
|
||||
settings := `{"clients":[` +
|
||||
`{"id":"66666666-6666-6666-6666-666666666666","email":"frank","subId":"s-frank","enable":false,"totalGB":5000}` +
|
||||
`],"decryption":"none","encryption":"none"}`
|
||||
// makeImportInbound forces Enable=true on every stats row, matching
|
||||
// controller.importInbound's behavior regardless of what's passed here.
|
||||
in := makeImportInbound("in-9104-tcp", 9104, settings, []xray.ClientTraffic{
|
||||
{Email: "frank", Up: 10, Down: 20, Total: 5000},
|
||||
})
|
||||
if _, _, err := svc.AddInbound(in); err != nil {
|
||||
t.Fatalf("import inbound: %v", err)
|
||||
}
|
||||
|
||||
var frank xray.ClientTraffic
|
||||
if err := database.GetDB().Where("email = ?", "frank").First(&frank).Error; err != nil {
|
||||
t.Fatalf("frank row: %v", err)
|
||||
}
|
||||
if !frank.Enable {
|
||||
t.Fatalf("frank.Enable = false, want true (import must force-enable even though Settings still says disabled)")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user