mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-06 10:06:36 +08:00
Merge branch 'main' into opencode/proud-planet
This commit is contained in:
@@ -2330,13 +2330,25 @@ func (r *Repository) exportTunnels() ([]TunnelBackup, error) {
|
|||||||
var tunnels []TunnelBackup
|
var tunnels []TunnelBackup
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var t TunnelBackup
|
var t TunnelBackup
|
||||||
|
var protocol sql.NullString
|
||||||
|
var updatedTime sql.NullInt64
|
||||||
var inIP sql.NullString
|
var inIP sql.NullString
|
||||||
if err := rows.Scan(&t.ID, &t.Name, &t.TrafficRatio, &t.Type, &t.Protocol, &t.Flow, &t.CreatedTime, &t.UpdatedTime, &t.Status, &inIP, &t.Inx); err != nil {
|
var inx sql.NullInt64
|
||||||
|
if err := rows.Scan(&t.ID, &t.Name, &t.TrafficRatio, &t.Type, &protocol, &t.Flow, &t.CreatedTime, &updatedTime, &t.Status, &inIP, &inx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if protocol.Valid {
|
||||||
|
t.Protocol = protocol.String
|
||||||
|
}
|
||||||
|
if updatedTime.Valid {
|
||||||
|
t.UpdatedTime = updatedTime.Int64
|
||||||
|
}
|
||||||
if inIP.Valid {
|
if inIP.Valid {
|
||||||
t.InIP = inIP.String
|
t.InIP = inIP.String
|
||||||
}
|
}
|
||||||
|
if inx.Valid {
|
||||||
|
t.Inx = int(inx.Int64)
|
||||||
|
}
|
||||||
// Export chain tunnels
|
// Export chain tunnels
|
||||||
chainTunnels, err := r.exportChainTunnels(t.ID)
|
chainTunnels, err := r.exportChainTunnels(t.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2362,12 +2374,23 @@ func (r *Repository) exportChainTunnels(tunnelID int64) ([]ChainTunnelBackup, er
|
|||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var ct ChainTunnelBackup
|
var ct ChainTunnelBackup
|
||||||
var port sql.NullInt64
|
var port sql.NullInt64
|
||||||
if err := rows.Scan(&ct.ID, &ct.TunnelID, &ct.ChainType, &ct.NodeID, &port, &ct.Strategy, &ct.Inx, &ct.Protocol); err != nil {
|
var strategy, protocol sql.NullString
|
||||||
|
var inx sql.NullInt64
|
||||||
|
if err := rows.Scan(&ct.ID, &ct.TunnelID, &ct.ChainType, &ct.NodeID, &port, &strategy, &inx, &protocol); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if port.Valid {
|
if port.Valid {
|
||||||
ct.Port = int(port.Int64)
|
ct.Port = int(port.Int64)
|
||||||
}
|
}
|
||||||
|
if strategy.Valid {
|
||||||
|
ct.Strategy = strategy.String
|
||||||
|
}
|
||||||
|
if inx.Valid {
|
||||||
|
ct.Inx = int(inx.Int64)
|
||||||
|
}
|
||||||
|
if protocol.Valid {
|
||||||
|
ct.Protocol = protocol.String
|
||||||
|
}
|
||||||
chainTunnels = append(chainTunnels, ct)
|
chainTunnels = append(chainTunnels, ct)
|
||||||
}
|
}
|
||||||
return chainTunnels, rows.Err()
|
return chainTunnels, rows.Err()
|
||||||
@@ -2386,9 +2409,21 @@ func (r *Repository) exportForwards() ([]ForwardBackup, error) {
|
|||||||
var forwards []ForwardBackup
|
var forwards []ForwardBackup
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var f ForwardBackup
|
var f ForwardBackup
|
||||||
if err := rows.Scan(&f.ID, &f.UserID, &f.UserName, &f.Name, &f.TunnelID, &f.RemoteAddr, &f.Strategy, &f.InFlow, &f.OutFlow, &f.CreatedTime, &f.UpdatedTime, &f.Status, &f.Inx); err != nil {
|
var strategy sql.NullString
|
||||||
|
var updatedTime sql.NullInt64
|
||||||
|
var inx sql.NullInt64
|
||||||
|
if err := rows.Scan(&f.ID, &f.UserID, &f.UserName, &f.Name, &f.TunnelID, &f.RemoteAddr, &strategy, &f.InFlow, &f.OutFlow, &f.CreatedTime, &updatedTime, &f.Status, &inx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if strategy.Valid {
|
||||||
|
f.Strategy = strategy.String
|
||||||
|
}
|
||||||
|
if updatedTime.Valid {
|
||||||
|
f.UpdatedTime = updatedTime.Int64
|
||||||
|
}
|
||||||
|
if inx.Valid {
|
||||||
|
f.Inx = int(inx.Int64)
|
||||||
|
}
|
||||||
forwards = append(forwards, f)
|
forwards = append(forwards, f)
|
||||||
}
|
}
|
||||||
return forwards, rows.Err()
|
return forwards, rows.Err()
|
||||||
|
|||||||
@@ -310,6 +310,80 @@ func TestBackupExportImportRestoreContracts(t *testing.T) {
|
|||||||
t.Fatalf("expected restored config value v3, got %+v", cfg)
|
t.Fatalf("expected restored config value v3, got %+v", cfg)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("backup export tolerates nullable legacy tunnel chain fields", func(t *testing.T) {
|
||||||
|
now := time.Now().UnixMilli()
|
||||||
|
res, err := repo.DB().Exec(`
|
||||||
|
INSERT INTO tunnel(name, traffic_ratio, type, protocol, flow, created_time, updated_time, status, in_ip, inx)
|
||||||
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`, "legacy-null-chain", 1.0, 1, "tls", 1000, now, now, 1, nil, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("seed tunnel for nullable chain export: %v", err)
|
||||||
|
}
|
||||||
|
tunnelID, err := res.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read tunnel id for nullable chain export: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := repo.DB().Exec(`
|
||||||
|
INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol)
|
||||||
|
VALUES(?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`, tunnelID, "1", 1, nil, nil, nil, nil); err != nil {
|
||||||
|
t.Fatalf("seed nullable chain_tunnel row: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/backup/export", bytes.NewBufferString(`{"types":["tunnels"]}`))
|
||||||
|
req.Header.Set("Authorization", adminToken)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(resp, req)
|
||||||
|
|
||||||
|
if resp.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected status 200, got %d", resp.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Tunnels []struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
ChainTunnels []struct {
|
||||||
|
Inx int `json:"inx"`
|
||||||
|
Strategy string `json:"strategy"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
} `json:"chainTunnels"`
|
||||||
|
} `json:"tunnels"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
||||||
|
t.Fatalf("decode tunnels backup payload: %v", err)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(payload.Version) == "" {
|
||||||
|
t.Fatalf("expected backup payload version, got empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, tunnel := range payload.Tunnels {
|
||||||
|
if tunnel.ID != tunnelID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(tunnel.ChainTunnels) != 1 {
|
||||||
|
t.Fatalf("expected one chain tunnel for seeded tunnel %d, got %d", tunnelID, len(tunnel.ChainTunnels))
|
||||||
|
}
|
||||||
|
if tunnel.ChainTunnels[0].Inx != 0 {
|
||||||
|
t.Fatalf("expected nullable chain inx to export as 0, got %d", tunnel.ChainTunnels[0].Inx)
|
||||||
|
}
|
||||||
|
if tunnel.ChainTunnels[0].Strategy != "" {
|
||||||
|
t.Fatalf("expected nullable chain strategy to export as empty string, got %q", tunnel.ChainTunnels[0].Strategy)
|
||||||
|
}
|
||||||
|
if tunnel.ChainTunnels[0].Protocol != "" {
|
||||||
|
t.Fatalf("expected nullable chain protocol to export as empty string, got %q", tunnel.ChainTunnels[0].Protocol)
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("expected seeded tunnel %d in backup export", tunnelID)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type backupExportPayload struct {
|
type backupExportPayload struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user