mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-20 14:41:05 +08:00
feat(go-api): add dataset tags endpoints (#16231)
## Summary - add `GET /api/v1/datasets/:dataset_id/tags` - add `PUT /api/v1/datasets/:dataset_id/tags` - implement dataset tag listing and rename flow - align rename tag validation and response shape with the Python API - add handler and service tests for dataset tags ## Routes - `GET /api/v1/datasets/:dataset_id/tags` - `PUT /api/v1/datasets/:dataset_id/tags` ## Test - Run specific tests for dataset tags: ``` go test -v ./internal/service ./internal/handler -run 'TestDatasetServiceListTags|TestDatasetServiceRenameTag|TestDatasetsHandlerListTags|TestDatasetsHandlerRenameTag' ``` - Run all tests for service and handler to verify no regressions: ``` go test ./internal/service ./internal/handler ``` - use curl cmd to test
This commit is contained in:
@@ -479,6 +479,77 @@ func (h *DatasetsHandler) GetKnowledgeGraph(c *gin.Context) {
|
||||
jsonResponse(c, common.CodeSuccess, result, "success")
|
||||
}
|
||||
|
||||
// ListTags handles GET /api/v1/datasets/:dataset_id/tags.
|
||||
// @Summary List dataset tags
|
||||
// @Description List tags for a dataset
|
||||
// @Tags datasets
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param dataset_id path string true "Dataset ID"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/datasets/{dataset_id}/tags [get]
|
||||
func (h *DatasetsHandler) ListTags(c *gin.Context) {
|
||||
user, errorCode, errorMessage := GetUser(c)
|
||||
if errorCode != common.CodeSuccess {
|
||||
jsonError(c, errorCode, errorMessage)
|
||||
return
|
||||
}
|
||||
|
||||
datasetID := strings.TrimSpace(c.Param("dataset_id"))
|
||||
result, code, err := h.datasetsService.ListTags(datasetID, user.ID)
|
||||
if err != nil {
|
||||
jsonError(c, code, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
jsonResponse(c, common.CodeSuccess, result, "success")
|
||||
}
|
||||
|
||||
type renameTagRequest struct {
|
||||
FromTag string `json:"from_tag"`
|
||||
ToTag string `json:"to_tag"`
|
||||
}
|
||||
|
||||
func (h *DatasetsHandler) RenameTag(c *gin.Context) {
|
||||
user, errorCode, errorMessage := GetUser(c)
|
||||
if errorCode != common.CodeSuccess {
|
||||
jsonError(c, errorCode, errorMessage)
|
||||
return
|
||||
}
|
||||
datasetID := strings.TrimSpace(c.Param("dataset_id"))
|
||||
|
||||
var payload map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
jsonError(c, common.CodeDataError, "Lack of from_tag or to_tag in request body")
|
||||
return
|
||||
}
|
||||
fromTagValue, hasFrom := payload["from_tag"]
|
||||
toTagValue, hasTo := payload["to_tag"]
|
||||
if !hasFrom || !hasTo {
|
||||
jsonError(c, common.CodeDataError, "Lack of from_tag or to_tag in request body")
|
||||
return
|
||||
}
|
||||
fromTag, okFrom := fromTagValue.(string)
|
||||
toTag, okTo := toTagValue.(string)
|
||||
if !okFrom || !okTo {
|
||||
jsonError(c, common.CodeArgumentError, "from_tag and to_tag must be strings")
|
||||
return
|
||||
}
|
||||
req := renameTagRequest{FromTag: fromTag, ToTag: toTag}
|
||||
if strings.TrimSpace(req.FromTag) == "" || strings.TrimSpace(req.ToTag) == "" {
|
||||
jsonError(c, common.CodeArgumentError, "from_tag and to_tag must not be empty")
|
||||
return
|
||||
}
|
||||
|
||||
result, code, err := h.datasetsService.RenameTag(datasetID, user.ID, req.FromTag, req.ToTag)
|
||||
if err != nil {
|
||||
jsonError(c, code, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
jsonResponse(c, common.CodeSuccess, result, "success")
|
||||
}
|
||||
|
||||
// DeleteKnowledgeGraph handles DELETE /api/v1/datasets/:dataset_id/graph.
|
||||
func (h *DatasetsHandler) DeleteKnowledgeGraph(c *gin.Context) {
|
||||
user, errorCode, errorMessage := GetUser(c)
|
||||
|
||||
39
internal/handler/datasets_list_tags_test.go
Normal file
39
internal/handler/datasets_list_tags_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"ragflow/internal/common"
|
||||
)
|
||||
|
||||
func TestDatasetsHandlerListTagsRequiresAuth(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
h := &DatasetsHandler{}
|
||||
r := gin.New()
|
||||
r.GET("/api/v1/datasets/:dataset_id/tags", func(c *gin.Context) {
|
||||
h.ListTags(c)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/datasets/123e4567-e89b-12d3-a456-426614174000/tags", nil)
|
||||
r.ServeHTTP(resp, req)
|
||||
|
||||
var body struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if err := json.Unmarshal(resp.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("unmarshal response: %v body=%s", err, resp.Body.String())
|
||||
}
|
||||
if body.Code != int(common.CodeUnauthorized) {
|
||||
t.Fatalf("code=%d want=%d body=%s", body.Code, common.CodeUnauthorized, resp.Body.String())
|
||||
}
|
||||
if body.Message != "User not found" {
|
||||
t.Fatalf("message=%q want=%q", body.Message, "User not found")
|
||||
}
|
||||
}
|
||||
126
internal/handler/datasets_rename_tag_test.go
Normal file
126
internal/handler/datasets_rename_tag_test.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"ragflow/internal/common"
|
||||
"ragflow/internal/entity"
|
||||
)
|
||||
|
||||
func TestDatasetsHandlerRenameTagRequiresAuth(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
h := &DatasetsHandler{}
|
||||
r := gin.New()
|
||||
r.PUT("/api/v1/datasets/:dataset_id/tags", func(c *gin.Context) {
|
||||
h.RenameTag(c)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/datasets/123e4567-e89b-12d3-a456-426614174000/tags", bytes.NewBufferString(`{"from_tag":"a","to_tag":"b"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(resp, req)
|
||||
|
||||
var body struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if err := json.Unmarshal(resp.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("unmarshal response: %v body=%s", err, resp.Body.String())
|
||||
}
|
||||
if body.Code != int(common.CodeUnauthorized) {
|
||||
t.Fatalf("code=%d want=%d body=%s", body.Code, common.CodeUnauthorized, resp.Body.String())
|
||||
}
|
||||
if body.Message != "User not found" {
|
||||
t.Fatalf("message=%q want=%q", body.Message, "User not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatasetsHandlerRenameTagRejectsMissingFields(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
h := &DatasetsHandler{}
|
||||
r := gin.New()
|
||||
r.PUT("/api/v1/datasets/:dataset_id/tags", func(c *gin.Context) {
|
||||
c.Set("user", &entity.User{ID: "user-1"})
|
||||
h.RenameTag(c)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/datasets/123e4567-e89b-12d3-a456-426614174000/tags", bytes.NewBufferString(`{"from_tag":"a"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(resp, req)
|
||||
|
||||
var body struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if err := json.Unmarshal(resp.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("unmarshal response: %v body=%s", err, resp.Body.String())
|
||||
}
|
||||
if body.Code != int(common.CodeDataError) {
|
||||
t.Fatalf("code=%d want=%d body=%s", body.Code, common.CodeDataError, resp.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatasetsHandlerRenameTagRejectsEmptyFields(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
h := &DatasetsHandler{}
|
||||
r := gin.New()
|
||||
r.PUT("/api/v1/datasets/:dataset_id/tags", func(c *gin.Context) {
|
||||
c.Set("user", &entity.User{ID: "user-1"})
|
||||
h.RenameTag(c)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/datasets/123e4567-e89b-12d3-a456-426614174000/tags", bytes.NewBufferString(`{"from_tag":" ","to_tag":"x"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(resp, req)
|
||||
|
||||
var body struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if err := json.Unmarshal(resp.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("unmarshal response: %v body=%s", err, resp.Body.String())
|
||||
}
|
||||
if body.Code != int(common.CodeArgumentError) {
|
||||
t.Fatalf("code=%d want=%d body=%s", body.Code, common.CodeArgumentError, resp.Body.String())
|
||||
}
|
||||
if body.Message != "from_tag and to_tag must not be empty" {
|
||||
t.Fatalf("message=%q want=%q", body.Message, "from_tag and to_tag must not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatasetsHandlerRenameTagRejectsNonStringFields(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
h := &DatasetsHandler{}
|
||||
r := gin.New()
|
||||
r.PUT("/api/v1/datasets/:dataset_id/tags", func(c *gin.Context) {
|
||||
c.Set("user", &entity.User{ID: "user-1"})
|
||||
h.RenameTag(c)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/datasets/123e4567-e89b-12d3-a456-426614174000/tags", bytes.NewBufferString(`{"from_tag":1,"to_tag":"x"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(resp, req)
|
||||
|
||||
var body struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if err := json.Unmarshal(resp.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("unmarshal response: %v body=%s", err, resp.Body.String())
|
||||
}
|
||||
if body.Code != int(common.CodeArgumentError) {
|
||||
t.Fatalf("code=%d want=%d body=%s", body.Code, common.CodeArgumentError, resp.Body.String())
|
||||
}
|
||||
if body.Message != "from_tag and to_tag must be strings" {
|
||||
t.Fatalf("message=%q want=%q", body.Message, "from_tag and to_tag must be strings")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user