From 0b000b833ebb66d06b8b0abcbb54faed1d1ef613 Mon Sep 17 00:00:00 2001 From: tmimmanuel <14046872+tmimmanuel@users.noreply.github.com> Date: Tue, 26 May 2026 20:07:55 -1000 Subject: [PATCH] Go: implement connector get API (#15259) ## Summary - Add Go REST support for `GET /api/v1/connectors/:connector_id`. - Reuse the Python API behavior by returning the connector only when the current user can access its tenant. - Add focused handler coverage for success and unauthorized responses. Co-authored-by: Jin Hai --- internal/handler/connector.go | 36 ++++++++++- internal/handler/connector_test.go | 97 ++++++++++++++++++++++++++++++ internal/router/router.go | 1 + internal/service/connector.go | 37 ++++++++++++ 4 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 internal/handler/connector_test.go diff --git a/internal/handler/connector.go b/internal/handler/connector.go index 5b1c5faf3c..e49ae66534 100644 --- a/internal/handler/connector.go +++ b/internal/handler/connector.go @@ -19,15 +19,21 @@ package handler import ( "net/http" "ragflow/internal/common" + "ragflow/internal/entity" "github.com/gin-gonic/gin" "ragflow/internal/service" ) +type connectorService interface { + ListConnectors(userID string) (*service.ListConnectorsResponse, error) + GetConnector(connectorID string, userID string) (*entity.Connector, common.ErrorCode, error) +} + // ConnectorHandler connector handler type ConnectorHandler struct { - connectorService *service.ConnectorService + connectorService connectorService userService *service.UserService } @@ -71,3 +77,31 @@ func (h *ConnectorHandler) ListConnectors(c *gin.Context) { "message": "success", }) } + +// GetConnector get connector +// @Summary Get Connector +// @Description Get connector details for the current user +// @Tags connector +// @Accept json +// @Produce json +// @Success 200 {object} map[string]interface{} +// @Router /api/v1/connectors/{connector_id} [get] +func (h *ConnectorHandler) GetConnector(c *gin.Context) { + user, errorCode, errorMessage := GetUser(c) + if errorCode != common.CodeSuccess { + jsonError(c, errorCode, errorMessage) + return + } + + connector, code, err := h.connectorService.GetConnector(c.Param("connector_id"), user.ID) + if err != nil { + jsonError(c, code, err.Error()) + return + } + + c.JSON(http.StatusOK, gin.H{ + "code": common.CodeSuccess, + "data": connector, + "message": "success", + }) +} diff --git a/internal/handler/connector_test.go b/internal/handler/connector_test.go new file mode 100644 index 0000000000..cd9afe1b6f --- /dev/null +++ b/internal/handler/connector_test.go @@ -0,0 +1,97 @@ +package handler + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + + "ragflow/internal/common" + "ragflow/internal/dao" + "ragflow/internal/entity" + "ragflow/internal/service" +) + +type fakeConnectorService struct { + connector *entity.Connector + code common.ErrorCode + err error +} + +func (s fakeConnectorService) ListConnectors(string) (*service.ListConnectorsResponse, error) { + return &service.ListConnectorsResponse{Connectors: []*dao.ConnectorListItem{}}, nil +} + +func (s fakeConnectorService) GetConnector(string, string) (*entity.Connector, common.ErrorCode, error) { + if s.err != nil { + return nil, s.code, s.err + } + return s.connector, common.CodeSuccess, nil +} + +func TestConnectorHandlerGetConnector(t *testing.T) { + gin.SetMode(gin.TestMode) + + tests := []struct { + name string + service fakeConnectorService + wantCode common.ErrorCode + wantID string + wantMsg string + }{ + { + name: "success", + service: fakeConnectorService{connector: &entity.Connector{ + ID: "connector-1", + TenantID: "tenant-1", + Name: "Docs", + Source: "google_drive", + Status: "unstart", + Config: entity.JSONMap{"folder": "docs"}, + }}, + wantCode: common.CodeSuccess, + wantID: "connector-1", + }, + { + name: "unauthorized", + service: fakeConnectorService{code: common.CodeAuthenticationError, err: fmt.Errorf("No authorization.")}, + wantCode: common.CodeAuthenticationError, + wantMsg: "No authorization.", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h := &ConnectorHandler{connectorService: tt.service} + router := gin.New() + router.GET("/api/v1/connectors/:connector_id", func(c *gin.Context) { + c.Set("user", &entity.User{ID: "tenant-1"}) + h.GetConnector(c) + }) + + resp := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/api/v1/connectors/connector-1", nil) + router.ServeHTTP(resp, req) + if resp.Code != http.StatusOK { + t.Fatalf("status=%d body=%s", resp.Code, resp.Body.String()) + } + + var body map[string]interface{} + if err := json.Unmarshal(resp.Body.Bytes(), &body); err != nil { + t.Fatalf("unmarshal response: %v", err) + } + if body["code"] != float64(tt.wantCode) { + t.Fatalf("code=%v body=%v", body["code"], body) + } + if tt.wantMsg != "" && body["message"] != tt.wantMsg { + t.Fatalf("message=%v", body["message"]) + } + if tt.wantID != "" && body["data"].(map[string]interface{})["id"] != tt.wantID { + t.Fatalf("data=%v", body["data"]) + } + }) + } +} diff --git a/internal/router/router.go b/internal/router/router.go index d343d8ee1b..7c01c9d95d 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -308,6 +308,7 @@ func (r *Router) Setup(engine *gin.Engine) { connector := v1.Group("/connectors") { connector.GET("/", r.connectorHandler.ListConnectors) + connector.GET("/:connector_id", r.connectorHandler.GetConnector) } system := v1.Group("/system") diff --git a/internal/service/connector.go b/internal/service/connector.go index bebf8e5e81..897899dd63 100644 --- a/internal/service/connector.go +++ b/internal/service/connector.go @@ -17,7 +17,13 @@ package service import ( + "errors" + "fmt" + "ragflow/internal/common" "ragflow/internal/dao" + "ragflow/internal/entity" + + "gorm.io/gorm" ) // ConnectorService connector service @@ -39,6 +45,37 @@ type ListConnectorsResponse struct { Connectors []*dao.ConnectorListItem `json:"connectors"` } +// GetConnector returns one connector when the user can access its tenant. +func (s *ConnectorService) GetConnector(connectorID string, userID string) (*entity.Connector, common.ErrorCode, error) { + if connectorID == "" { + return nil, common.CodeDataError, fmt.Errorf("connector_id is required") + } + + connector, err := s.connectorDAO.GetByID(connectorID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, common.CodeAuthenticationError, fmt.Errorf("No authorization.") + } + return nil, common.CodeServerError, err + } + + if connector.TenantID == userID { + return connector, common.CodeSuccess, nil + } + + tenantIDs, err := s.userTenantDAO.GetTenantIDsByUserID(userID) + if err != nil { + return nil, common.CodeServerError, err + } + for _, tenantID := range tenantIDs { + if tenantID == connector.TenantID { + return connector, common.CodeSuccess, nil + } + } + + return nil, common.CodeAuthenticationError, fmt.Errorf("No authorization.") +} + // ListConnectors list connectors for a user // Equivalent to Python's ConnectorService.list(current_user.id) func (s *ConnectorService) ListConnectors(userID string) (*ListConnectorsResponse, error) {