Преглед изворни кода

fix(client): honor keepTraffic when deleting a client that is attached to inbounds

Delete, DeleteByEmail and BulkDelete all pass keepTraffic to their final
cleanup transaction, but each called the per-inbound delete helper with a
hardcoded false. That helper purges the client's traffic, IP and stat rows
before the gated cleanup runs, so keepTraffic=true still destroyed all
traffic history for any client actually attached to an inbound (the pinned
test only covered a record with no inbound mappings). Thread the caller's
keepTraffic through to the per-inbound helper at all three call sites.
MHSanaei пре 1 дан
родитељ
комит
0bbcd2a8f2

+ 27 - 0
internal/web/service/bulk_traffic_test.go

@@ -119,6 +119,33 @@ func TestDelDepletedRemovesOnlyDepleted(t *testing.T) {
 	}
 }
 
+func TestClientDeleteKeepTrafficPreservesRowForAttachedClient(t *testing.T) {
+	setupBulkDB(t)
+	svc := &ClientService{}
+	inboundSvc := &InboundService{}
+
+	email := "keepme@x"
+	c := model.Client{Email: email, ID: "11111111-1111-1111-1111-111111111111", SubID: email, Enable: true}
+	ib := mkInbound(t, 52030, model.VLESS, clientsSettings(t, []model.Client{c}))
+	if err := svc.SyncInbound(nil, ib.Id, []model.Client{c}); err != nil {
+		t.Fatalf("seed linkage: %v", err)
+	}
+	mkTraffic(t, ib.Id, email, 111, 222, 0, 0, true)
+
+	rec := lookupClientRecord(t, email)
+	if _, err := svc.Delete(inboundSvc, rec.Id, true); err != nil {
+		t.Fatalf("Delete(keepTraffic): %v", err)
+	}
+
+	var cnt int64
+	if err := database.GetDB().Model(&xray.ClientTraffic{}).Where("email = ?", email).Count(&cnt).Error; err != nil {
+		t.Fatalf("count traffic: %v", err)
+	}
+	if cnt != 1 {
+		t.Fatalf("keepTraffic delete of an inbound-attached client must preserve its client_traffics row, found %d", cnt)
+	}
+}
+
 func TestGetClientTrafficByEmailReadsClientsTable(t *testing.T) {
 	setupBulkDB(t)
 	svc := &ClientService{}

+ 1 - 1
internal/web/service/client_bulk.go

@@ -823,7 +823,7 @@ func (s *ClientService) BulkDelete(inboundSvc *InboundService, emails []string,
 
 	needRestart := false
 	for inboundId, ibEmails := range emailsByInbound {
-		ibResult := s.bulkDelInboundClients(inboundSvc, inboundId, ibEmails, recordsByEmail, false)
+		ibResult := s.bulkDelInboundClients(inboundSvc, inboundId, ibEmails, recordsByEmail, keepTraffic)
 		if ibResult.needRestart {
 			needRestart = true
 		}

+ 2 - 2
internal/web/service/client_crud.go

@@ -510,7 +510,7 @@ func (s *ClientService) Delete(inboundSvc *InboundService, id int, keepTraffic b
 		if existing.Email == "" {
 			continue
 		}
-		nr, delErr := s.DelInboundClientByEmail(inboundSvc, ibId, existing.Email, false, true)
+		nr, delErr := s.DelInboundClientByEmail(inboundSvc, ibId, existing.Email, keepTraffic, true)
 		if delErr != nil {
 			// The client is already absent from this inbound (data drift or a
 			// retried delete). Skip it — deletion stays idempotent.
@@ -678,7 +678,7 @@ func (s *ClientService) DeleteByEmail(inboundSvc *InboundService, email string,
 	needRestart := false
 	var delErrs []error
 	for _, ibId := range inboundIds {
-		nr, delErr := s.DelInboundClientByEmail(inboundSvc, ibId, email, false, true)
+		nr, delErr := s.DelInboundClientByEmail(inboundSvc, ibId, email, keepTraffic, true)
 		if delErr != nil {
 			if errors.Is(delErr, ErrClientNotInInbound) {
 				continue