1
0

inbound_settings_commit.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package service
  2. import (
  3. "encoding/json"
  4. "reflect"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  6. "gorm.io/gorm"
  7. )
  8. // commitInboundClientSettings writes a client op's edit (base → ib.Settings) onto
  9. // the settings committed now: a traffic tick after the op's read must survive.
  10. func commitInboundClientSettings(tx *gorm.DB, ib *model.Inbound, base string) error {
  11. var current []string
  12. if err := tx.Model(&model.Inbound{}).Where("id = ?", ib.Id).Pluck("settings", &current).Error; err != nil {
  13. return err
  14. }
  15. if len(current) == 1 && current[0] != base {
  16. merged, err := rebaseClientSettings(base, ib.Settings, current[0])
  17. if err != nil {
  18. return err
  19. }
  20. ib.Settings = merged
  21. }
  22. return tx.Model(&model.Inbound{}).Where("id = ?", ib.Id).Update("settings", ib.Settings).Error
  23. }
  24. // rebaseClientSettings three-way merges settings JSON: every key and client field
  25. // ours left as base had it takes current's value; clients are matched by email.
  26. func rebaseClientSettings(base, ours, current string) (string, error) {
  27. var baseM, oursM, curM map[string]any
  28. for _, p := range []struct {
  29. raw string
  30. dst *map[string]any
  31. }{{base, &baseM}, {ours, &oursM}, {current, &curM}} {
  32. if err := json.Unmarshal([]byte(p.raw), p.dst); err != nil {
  33. return "", err
  34. }
  35. }
  36. out := mergeFields(baseM, oursM, curM)
  37. baseClients, _ := baseM["clients"].([]any)
  38. oursClients, _ := oursM["clients"].([]any)
  39. curClients, _ := curM["clients"].([]any)
  40. if _, has := oursM["clients"]; has {
  41. out["clients"] = mergeClientLists(baseClients, oursClients, curClients)
  42. }
  43. b, err := json.MarshalIndent(out, "", " ")
  44. if err != nil {
  45. return "", err
  46. }
  47. return string(b), nil
  48. }
  49. func mergeFields(base, ours, current map[string]any) map[string]any {
  50. out := make(map[string]any, len(current)+len(ours))
  51. for k, v := range current {
  52. out[k] = v
  53. }
  54. keys := make(map[string]struct{}, len(base)+len(ours))
  55. for k := range base {
  56. keys[k] = struct{}{}
  57. }
  58. for k := range ours {
  59. keys[k] = struct{}{}
  60. }
  61. for k := range keys {
  62. bv, inBase := base[k]
  63. ov, inOurs := ours[k]
  64. if inBase == inOurs && reflect.DeepEqual(bv, ov) {
  65. continue
  66. }
  67. if inOurs {
  68. out[k] = ov
  69. } else {
  70. delete(out, k)
  71. }
  72. }
  73. return out
  74. }
  75. func clientEntryEmail(entry any) (map[string]any, string) {
  76. m, ok := entry.(map[string]any)
  77. if !ok {
  78. return nil, ""
  79. }
  80. email, _ := m["email"].(string)
  81. return m, email
  82. }
  83. func indexClientsByEmail(list []any) map[string]map[string]any {
  84. out := make(map[string]map[string]any, len(list))
  85. for _, entry := range list {
  86. if m, email := clientEntryEmail(entry); email != "" {
  87. out[email] = m
  88. }
  89. }
  90. return out
  91. }
  92. // mergeClientLists keeps ours' order. A client ours removed stays removed; one a
  93. // concurrent writer added or removed keeps that change unless ours edited it.
  94. func mergeClientLists(base, ours, current []any) []any {
  95. baseBy := indexClientsByEmail(base)
  96. curBy := indexClientsByEmail(current)
  97. out := make([]any, 0, len(ours)+len(current))
  98. placed := make(map[string]struct{}, len(ours))
  99. for _, entry := range ours {
  100. o, email := clientEntryEmail(entry)
  101. if email == "" {
  102. out = append(out, entry)
  103. continue
  104. }
  105. placed[email] = struct{}{}
  106. b, inBase := baseBy[email]
  107. c, inCur := curBy[email]
  108. switch {
  109. case !inBase:
  110. out = append(out, o)
  111. case !inCur:
  112. if !reflect.DeepEqual(b, o) {
  113. out = append(out, o)
  114. }
  115. default:
  116. out = append(out, mergeFields(b, o, c))
  117. }
  118. }
  119. for _, entry := range current {
  120. c, email := clientEntryEmail(entry)
  121. if email == "" {
  122. continue
  123. }
  124. if _, done := placed[email]; done {
  125. continue
  126. }
  127. if _, inBase := baseBy[email]; !inBase {
  128. out = append(out, c)
  129. }
  130. }
  131. return out
  132. }
  133. // Client limits and state the inbound form never owns: the client endpoints and
  134. // traffic jobs change them, so a form opened earlier must not write them back.
  135. var storedClientLifecycleKeys = []string{"enable", "expiryTime", "totalGB", "reset", "resetDay", "resetWeekday", "resetMax"}
  136. // keepStoredClientLifecycle copies those keys from the stored settings onto every
  137. // payload client the inbound already holds; new clients keep what they carry.
  138. func keepStoredClientLifecycle(payload, stored string) string {
  139. var payloadM, storedM map[string]any
  140. if json.Unmarshal([]byte(payload), &payloadM) != nil || json.Unmarshal([]byte(stored), &storedM) != nil {
  141. return payload
  142. }
  143. storedClients, _ := storedM["clients"].([]any)
  144. storedBy := indexClientsByEmail(storedClients)
  145. payloadClients, _ := payloadM["clients"].([]any)
  146. changed := false
  147. for _, entry := range payloadClients {
  148. p, email := clientEntryEmail(entry)
  149. s, known := storedBy[email]
  150. if !known {
  151. continue
  152. }
  153. for _, key := range storedClientLifecycleKeys {
  154. sv, inStored := s[key]
  155. pv, inPayload := p[key]
  156. if inStored == inPayload && reflect.DeepEqual(sv, pv) {
  157. continue
  158. }
  159. changed = true
  160. if inStored {
  161. p[key] = sv
  162. } else {
  163. delete(p, key)
  164. }
  165. }
  166. }
  167. if !changed {
  168. return payload
  169. }
  170. b, err := json.MarshalIndent(payloadM, "", " ")
  171. if err != nil {
  172. return payload
  173. }
  174. return string(b)
  175. }