feat(backend): add created_at/updated_at to clients and maintain on create/update

backfill existing clients and set updated_at on mutations
This commit is contained in:
Ali Golzar 2025-08-27 02:26:42 +03:30
parent d10c312e62
commit 4ea3e686c2
2 changed files with 125 additions and 0 deletions

View file

@ -104,4 +104,6 @@ type Client struct {
SubID string `json:"subId" form:"subId"` SubID string `json:"subId" form:"subId"`
Comment string `json:"comment" form:"comment"` Comment string `json:"comment" form:"comment"`
Reset int `json:"reset" form:"reset"` Reset int `json:"reset" form:"reset"`
CreatedAt int64 `json:"created_at,omitempty"`
UpdatedAt int64 `json:"updated_at,omitempty"`
} }

View file

@ -175,6 +175,30 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
return inbound, false, err return inbound, false, err
} }
// Ensure created_at and updated_at on clients in settings
if len(clients) > 0 {
var settings map[string]any
if err2 := json.Unmarshal([]byte(inbound.Settings), &settings); err2 == nil && settings != nil {
now := time.Now().Unix() * 1000
updatedClients := make([]model.Client, 0, len(clients))
for _, c := range clients {
if c.CreatedAt == 0 {
c.CreatedAt = now
}
c.UpdatedAt = now
updatedClients = append(updatedClients, c)
}
settings["clients"] = updatedClients
if bs, err3 := json.MarshalIndent(settings, "", " "); err3 == nil {
inbound.Settings = string(bs)
} else {
logger.Debug("Unable to marshal inbound settings with timestamps:", err3)
}
} else if err2 != nil {
logger.Debug("Unable to parse inbound settings for timestamps:", err2)
}
}
// Secure client ID // Secure client ID
for _, client := range clients { for _, client := range clients {
switch inbound.Protocol { switch inbound.Protocol {
@ -320,6 +344,53 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
return inbound, false, err return inbound, false, err
} }
// Ensure created_at and updated_at exist in inbound.Settings clients
{
var oldSettings map[string]any
_ = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
emailToCreated := map[string]int64{}
if oldSettings != nil {
if oc, ok := oldSettings["clients"].([]any); ok {
for _, it := range oc {
if m, ok2 := it.(map[string]any); ok2 {
if email, ok3 := m["email"].(string); ok3 {
switch v := m["created_at"].(type) {
case float64:
emailToCreated[email] = int64(v)
case int64:
emailToCreated[email] = v
}
}
}
}
}
}
var newSettings map[string]any
if err2 := json.Unmarshal([]byte(inbound.Settings), &newSettings); err2 == nil && newSettings != nil {
now := time.Now().Unix() * 1000
if nSlice, ok := newSettings["clients"].([]any); ok {
for i := range nSlice {
if m, ok2 := nSlice[i].(map[string]any); ok2 {
email, _ := m["email"].(string)
if _, ok3 := m["created_at"]; !ok3 {
if v, ok4 := emailToCreated[email]; ok4 && v > 0 {
m["created_at"] = v
} else {
m["created_at"] = now
}
}
m["updated_at"] = now
nSlice[i] = m
}
}
newSettings["clients"] = nSlice
if bs, err3 := json.MarshalIndent(newSettings, "", " "); err3 == nil {
inbound.Settings = string(bs)
}
}
}
}
oldInbound.Up = inbound.Up oldInbound.Up = inbound.Up
oldInbound.Down = inbound.Down oldInbound.Down = inbound.Down
oldInbound.Total = inbound.Total oldInbound.Total = inbound.Total
@ -422,6 +493,17 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
} }
interfaceClients := settings["clients"].([]any) interfaceClients := settings["clients"].([]any)
// Add timestamps for new clients being appended
nowTs := time.Now().Unix() * 1000
for i := range interfaceClients {
if cm, ok := interfaceClients[i].(map[string]any); ok {
if _, ok2 := cm["created_at"]; !ok2 {
cm["created_at"] = nowTs
}
cm["updated_at"] = nowTs
interfaceClients[i] = cm
}
}
existEmail, err := s.checkEmailsExistForClients(clients) existEmail, err := s.checkEmailsExistForClients(clients)
if err != nil { if err != nil {
return false, err return false, err
@ -672,6 +754,25 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
return false, err return false, err
} }
settingsClients := oldSettings["clients"].([]any) settingsClients := oldSettings["clients"].([]any)
// Preserve created_at and set updated_at for the replacing client
var preservedCreated any
if clientIndex >= 0 && clientIndex < len(settingsClients) {
if oldMap, ok := settingsClients[clientIndex].(map[string]any); ok {
if v, ok2 := oldMap["created_at"]; ok2 {
preservedCreated = v
}
}
}
if len(interfaceClients) > 0 {
if newMap, ok := interfaceClients[0].(map[string]any); ok {
if preservedCreated == nil {
preservedCreated = time.Now().Unix() * 1000
}
newMap["created_at"] = preservedCreated
newMap["updated_at"] = time.Now().Unix() * 1000
interfaceClients[0] = newMap
}
}
settingsClients[clientIndex] = interfaceClients[0] settingsClients[clientIndex] = interfaceClients[0]
oldSettings["clients"] = settingsClients oldSettings["clients"] = settingsClients
@ -909,10 +1010,22 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
oldExpiryTime := c["expiryTime"].(float64) oldExpiryTime := c["expiryTime"].(float64)
newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime) newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime)
c["expiryTime"] = newExpiryTime c["expiryTime"] = newExpiryTime
c["updated_at"] = time.Now().Unix() * 1000
dbClientTraffics[traffic_index].ExpiryTime = newExpiryTime dbClientTraffics[traffic_index].ExpiryTime = newExpiryTime
break break
} }
} }
// Remove "flow": "xtls-rprx-direct"
if _, ok := c["flow"]; ok {
if c["flow"] == "xtls-rprx-direct" {
c["flow"] = ""
}
}
// Backfill created_at and updated_at
if _, ok := c["created_at"]; !ok {
c["created_at"] = time.Now().Unix() * 1000
}
c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) newClients = append(newClients, any(c))
} }
settings["clients"] = newClients settings["clients"] = newClients
@ -1274,6 +1387,7 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (boo
c := clients[client_index].(map[string]any) c := clients[client_index].(map[string]any)
if c["email"] == clientEmail { if c["email"] == clientEmail {
c["tgId"] = tgId c["tgId"] = tgId
c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) newClients = append(newClients, any(c))
} }
} }
@ -1360,6 +1474,7 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bo
c := clients[client_index].(map[string]any) c := clients[client_index].(map[string]any)
if c["email"] == clientEmail { if c["email"] == clientEmail {
c["enable"] = !clientOldEnabled c["enable"] = !clientOldEnabled
c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) newClients = append(newClients, any(c))
} }
} }
@ -1423,6 +1538,7 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
c := clients[client_index].(map[string]any) c := clients[client_index].(map[string]any)
if c["email"] == clientEmail { if c["email"] == clientEmail {
c["limitIp"] = count c["limitIp"] = count
c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) newClients = append(newClients, any(c))
} }
} }
@ -1481,6 +1597,7 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
c := clients[client_index].(map[string]any) c := clients[client_index].(map[string]any)
if c["email"] == clientEmail { if c["email"] == clientEmail {
c["expiryTime"] = expiry_time c["expiryTime"] = expiry_time
c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) newClients = append(newClients, any(c))
} }
} }
@ -1542,6 +1659,7 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
c := clients[client_index].(map[string]any) c := clients[client_index].(map[string]any)
if c["email"] == clientEmail { if c["email"] == clientEmail {
c["totalGB"] = totalGB * 1024 * 1024 * 1024 c["totalGB"] = totalGB * 1024 * 1024 * 1024
c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) newClients = append(newClients, any(c))
} }
} }
@ -1962,6 +2080,11 @@ func (s *InboundService) MigrationRequirements() {
c["flow"] = "" c["flow"] = ""
} }
} }
// Backfill created_at and updated_at
if _, ok := c["created_at"]; !ok {
c["created_at"] = time.Now().Unix() * 1000
}
c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) newClients = append(newClients, any(c))
} }
settings["clients"] = newClients settings["clients"] = newClients