package api import ( "io" "net/http" "strconv" "strings" "github.com/gin-gonic/gin" "github.com/prism/proxy/internal/proxyio" "github.com/prism/proxy/internal/store" ) func (s *Server) exportProxies(c *gin.Context) { ctx := c.Request.Context() items, err := s.app.Store.ListProxyNodes(ctx, "manual", nil) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } format := strings.ToLower(c.DefaultQuery("format", "json")) filename := "prism-proxies" switch format { case "text", "txt", "links": c.Header("Content-Type", "text/plain; charset=utf-8") c.Header("Content-Disposition", `attachment; filename="`+filename+`.txt"`) c.String(http.StatusOK, proxyio.ExportLinks(items)) default: data, err := proxyio.ExportJSON(items) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.Header("Content-Type", "application/json; charset=utf-8") c.Header("Content-Disposition", `attachment; filename="`+filename+`.json"`) c.Data(http.StatusOK, "application/json; charset=utf-8", data) } } type importProxiesReq struct { Mode string `json:"mode"` Text string `json:"text"` Nodes []proxyio.ExportNode `json:"nodes"` } func (s *Server) importProxies(c *gin.Context) { ctx := c.Request.Context() mode := strings.ToLower(strings.TrimSpace(c.DefaultQuery("mode", ""))) var nodes []store.ProxyNode var parseErrors []string contentType := c.GetHeader("Content-Type") if strings.Contains(contentType, "application/json") { var req importProxiesReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if mode == "" { mode = strings.ToLower(strings.TrimSpace(req.Mode)) } if len(req.Nodes) > 0 { var valErrors []string nodes, valErrors = proxyio.ToStoreNodes(req.Nodes) parseErrors = append(parseErrors, valErrors...) } else if strings.TrimSpace(req.Text) != "" { nodes, parseErrors = proxyio.ParseText(req.Text) } else { c.JSON(http.StatusBadRequest, gin.H{"error": "nodes or text required"}) return } } else { body, err := io.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if mode == "" { mode = "append" } nodes, parseErrors = proxyio.ParseText(string(body)) } if mode == "" { mode = "append" } if mode != "append" && mode != "replace" { c.JSON(http.StatusBadRequest, gin.H{"error": "mode must be append or replace"}) return } if len(nodes) == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "no valid proxies", "errors": parseErrors}) return } if mode == "append" { existing, _ := s.app.Store.ListProxyNodes(ctx, "manual", nil) proxyio.AssignUniqueNames(nodes, proxyio.ReservedNames(existing)) if err := s.app.Store.AppendManualProxies(ctx, nodes); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } } else { proxyio.AssignUniqueNames(nodes, nil) if err := s.app.Store.ReplaceManualProxies(ctx, nodes); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } } _ = s.app.Store.AddAuditLog(ctx, "import", "proxies", mode+": "+strconv.Itoa(len(nodes))) _ = s.app.ReloadEngine(ctx) c.JSON(http.StatusOK, gin.H{ "ok": true, "imported": len(nodes), "skipped": len(parseErrors), "errors": parseErrors, "mode": mode, }) }