package handlers import ( "auto-ssl/config" "encoding/json" "net/http" "strconv" "github.com/gin-gonic/gin" ) type CredentialHandler struct{} func NewCredentialHandler() *CredentialHandler { return &CredentialHandler{} } type CreateCredentialRequest struct { Name string `json:"name" binding:"required"` Type config.CredentialType `json:"type" binding:"required"` Data string `json:"data" binding:"required"` // JSON key-value pairs } type UpdateCredentialRequest struct { Name string `json:"name"` Type config.CredentialType `json:"type"` Data string `json:"data"` // JSON key-value pairs (or "********" to keep) } // ListCredentials returns all credentials (without secrets) func (h *CredentialHandler) ListCredentials(c *gin.Context) { creds := config.CredStore.GetAll() c.JSON(http.StatusOK, creds) } // GetCredential returns a single credential func (h *CredentialHandler) GetCredential(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } cred := config.CredStore.GetByID(uint(id)) if cred == nil { c.JSON(http.StatusNotFound, gin.H{"error": "credential not found"}) return } // Never expose raw secrets in API cred.Data = cred.Masked c.JSON(http.StatusOK, cred) } // CreateCredential creates a new credential func (h *CredentialHandler) CreateCredential(c *gin.Context) { var req CreateCredentialRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Validate JSON data var testData map[string]string if err := json.Unmarshal([]byte(req.Data), &testData); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "data must be valid JSON key-value pairs"}) return } // Validate fields for the provider type fields := config.CredentialTypeMeta(req.Type) for _, f := range fields { if f.Required { if _, ok := testData[f.Key]; !ok || testData[f.Key] == "" { c.JSON(http.StatusBadRequest, gin.H{"error": f.Label + " 是必填项"}) return } } } cred := &config.Credential{ Name: req.Name, Type: req.Type, Data: req.Data, } if err := config.CredStore.Create(cred); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Return without secrets cred.Data = cred.Masked c.JSON(http.StatusCreated, cred) } // UpdateCredential updates a credential func (h *CredentialHandler) UpdateCredential(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } existing := config.CredStore.GetByID(uint(id)) if existing == nil { c.JSON(http.StatusNotFound, gin.H{"error": "credential not found"}) return } var req UpdateCredentialRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if req.Name != "" { existing.Name = req.Name } if req.Type != "" { existing.Type = req.Type } if req.Data != "" { // If it's the masked placeholder, keep existing data if req.Data == "********" { existing.Data = existing.Data } else { var testData map[string]string if err := json.Unmarshal([]byte(req.Data), &testData); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "data must be valid JSON"}) return } existing.Data = req.Data } } if err := config.CredStore.Update(existing); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } existing.Data = existing.Masked c.JSON(http.StatusOK, existing) } // DeleteCredential deletes a credential func (h *CredentialHandler) DeleteCredential(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } if err := config.CredStore.Delete(uint(id)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "credential deleted"}) } // GetCredentialTypes returns available credential types and their fields func (h *CredentialHandler) GetCredentialTypes(c *gin.Context) { types := []gin.H{ { "type": config.CredentialAliDNS, "label": "阿里云 DNS (Aliyun)", "fields": config.CredentialTypeMeta(config.CredentialAliDNS), }, { "type": config.CredentialCloudflare, "label": "Cloudflare", "fields": config.CredentialTypeMeta(config.CredentialCloudflare), }, { "type": config.CredentialDNSPod, "label": "DNSPod", "fields": config.CredentialTypeMeta(config.CredentialDNSPod), }, } c.JSON(http.StatusOK, types) }