package handlers import ( "encoding/json" "net/http" "github.com/rede5/gohorsejobs/backend/internal/services" ) type CredentialsHandler struct { credentialsService *services.CredentialsService } func NewCredentialsHandler(s *services.CredentialsService) *CredentialsHandler { return &CredentialsHandler{credentialsService: s} } // ListCredentials returns a list of configured services (metadata only, no secrets) // GET /api/v1/system/credentials func (h *CredentialsHandler) ListCredentials(w http.ResponseWriter, r *http.Request) { services, err := h.credentialsService.ListConfiguredServices(r.Context()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } response := map[string]interface{}{ "services": services, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } // SaveCredential saves encrypted credentials for a service // POST /api/v1/system/credentials func (h *CredentialsHandler) SaveCredential(w http.ResponseWriter, r *http.Request) { var req struct { ServiceName string `json:"serviceName"` Payload interface{} `json:"payload"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if req.ServiceName == "" { http.Error(w, "Service name is required", http.StatusBadRequest) return } // Marshaling the payload to JSON string before encryption payloadBytes, err := json.Marshal(req.Payload) if err != nil { http.Error(w, "Invalid payload format", http.StatusBadRequest) return } // Encrypt using the service encryptedPayload, err := h.credentialsService.EncryptPayload(string(payloadBytes)) if err != nil { http.Error(w, "Failed to encrypt credentials: "+err.Error(), http.StatusInternalServerError) return } // Determine user making the change (from context or default) updatedBy := "admin" // TODO: Extract from context keys if available if err := h.credentialsService.SaveCredentials(r.Context(), req.ServiceName, encryptedPayload, updatedBy); err != nil { http.Error(w, "Failed to save credentials: "+err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"message": "Credentials saved successfully"}) } // DeleteCredential removes credentials for a service // DELETE /api/v1/system/credentials/{service} func (h *CredentialsHandler) DeleteCredential(w http.ResponseWriter, r *http.Request) { serviceName := r.PathValue("service") if serviceName == "" { http.Error(w, "Service name is required", http.StatusBadRequest) return } if err := h.credentialsService.DeleteCredentials(r.Context(), serviceName); err != nil { http.Error(w, "Failed to delete credentials: "+err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"message": "Credentials deleted successfully"}) } // TestCredential runs a connection test for the provided service // POST /api/v1/system/credentials/{service}/test func (h *CredentialsHandler) TestCredential(w http.ResponseWriter, r *http.Request) { serviceName := r.PathValue("service") if serviceName == "" { http.Error(w, "Service name is required", http.StatusBadRequest) return } // Handle specific existing endpoints independently if they don't map to the new CredentialsService logic if serviceName == "storage" { http.Error(w, "Please use the /api/v1/admin/storage/test-connection endpoint for storage", http.StatusBadRequest) return } if err := h.credentialsService.TestConnection(r.Context(), serviceName); err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"error": "Test failed: " + err.Error()}) return } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"message": "Connection test successful"}) }