package app import ( "fmt" "strings" "time" ) func buildTransportClientFromCreate(body TransportClientCreateRequest) (TransportClient, error) { id := sanitizeID(body.ID) if id == "" { return TransportClient{}, fmt.Errorf("invalid id") } kind := normalizeTransportKind(body.Kind) if kind == "" { return TransportClient{}, fmt.Errorf("kind must be singbox|dnstt|phoenix") } enabled := true if body.Enabled != nil { enabled = *body.Enabled } name := strings.TrimSpace(body.Name) if name == "" { name = id } ifaceID := normalizeTransportIfaceID(body.IfaceID) client := TransportClient{ ID: id, Name: name, Kind: kind, Enabled: enabled, Status: TransportClientDown, IfaceID: ifaceID, RoutingTable: transportRoutingTableForID(id), Capabilities: defaultTransportCapabilities(kind), Config: cloneMap(body.Config), UpdatedAt: time.Now().UTC().Format(time.RFC3339), Runtime: TransportClientRuntime{ Backend: "", AllowedActions: []string{"provision", "start", "stop", "restart"}, LastExitCode: 0, }, } if normCfg, _ := normalizeTransportClientConfig(client.Kind, client.Config); normCfg != nil || client.Config != nil { client.Config = normCfg } client.Runtime.Backend = selectTransportBackend(client).ID() client.Health = TransportClientHealth{ LastCheck: client.UpdatedAt, } return client, nil } func normalizeTransportKind(k TransportClientKind) TransportClientKind { switch strings.ToLower(strings.TrimSpace(string(k))) { case string(TransportClientSingBox): return TransportClientSingBox case string(TransportClientDNSTT): return TransportClientDNSTT case string(TransportClientPhoenix): return TransportClientPhoenix default: return "" } } func defaultTransportCapabilities(kind TransportClientKind) []string { switch kind { case TransportClientSingBox: return []string{"tcp", "udp", "dns_tunnel"} case TransportClientDNSTT: return []string{"tcp", "dns_tunnel"} case TransportClientPhoenix: return []string{"tcp", "udp", "ssh_tunnel"} default: return nil } }