Refactor: Delete not used APIs (#20894)

This commit is contained in:
Wang Qi authored and GitHub committed 2026-10-13 17:48:55 +08:00
1 parent 2cfc74f8bd
commit 863fee004c
18 files changed
+139 -688

No files matched your search

+1 -6
View File
@@ -2623,12 +2623,7 @@ func (c *CLI) DevUpdateChunkCommand(commandCount int, cmd *Command) (ResponseIf,
return nil, fmt.Errorf("invalid JSON body: %w", err)
}
// Add IDs to payload
payload["dataset_id"] = datasetID
payload["document_id"] = docID
payload["chunk_id"] = chunkID
resp, err := httpClient.Request(commandCount, "POST", "/chunk/update", httpClient.AuthKind(), nil, payload)
resp, err := httpClient.Request(commandCount, "PATCH", utils.APIPath("/datasets", datasetID, "documents", docID, "chunks", chunkID), httpClient.AuthKind(), nil, payload)
if err != nil {
return nil, fmt.Errorf("failed to update chunk: %w", err)
}
+79
View File
@@ -0,0 +1,79 @@
//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package cli
import (
"encoding/json"
"fmt"
"net/http"
"testing"
)
func TestDevUpdateChunkCommandUsesChunkResource(t *testing.T) {
updated := make(chan struct{}, 1)
client := newTestHTTPClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.URL.Path {
case "/api/v1/datasets":
if r.Method != http.MethodGet {
t.Errorf("dataset lookup method = %s, want GET", r.Method)
}
_, _ = fmt.Fprint(w, `{"code":0,"data":[{"id":"dataset-1","name":"test"}]}`)
case "/api/v1/datasets/dataset-1/documents/doc-1/chunks/chunk-1":
if r.Method != http.MethodPatch {
t.Errorf("update method = %s, want PATCH", r.Method)
}
var payload map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
t.Errorf("decode update: %v", err)
}
if payload["content"] != "updated" || payload["available"] != false {
t.Errorf("unexpected update payload: %#v", payload)
}
updated <- struct{}{}
_, _ = fmt.Fprint(w, `{"code":0,"message":"success"}`)
default:
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
http.NotFound(w, r)
}
}))
client.useAPIKey = true
c := &CLI{
Config: &CommandLineConfig{
CLIMode: APIMode,
APIClientConfig: APIModeConfig{CurrentAPIServer: "test"},
},
APIServerClientMap: map[string]*HTTPClient{"test": client},
}
result, err := c.DevUpdateChunkCommand(1, &Command{Params: map[string]interface{}{
"dataset_name": "test",
"doc_id": "doc-1",
"chunk_id": "chunk-1",
"json_body": `{"content":"updated","available":false}`,
}})
if err != nil {
t.Fatal(err)
}
select {
case <-updated:
default:
t.Fatal("chunk update was not sent")
}
if response, ok := result.(*SimpleResponse); !ok || response.Code != 0 {
t.Fatalf("unexpected response: %#v", result)
}
}
+2 -46
View File
@@ -361,50 +361,6 @@ func (h *ChunkHandler) StopParsing(c *gin.Context) {
common.SuccessWithData(c, data, message)
}
// List retrieves chunks for a document.
// @Summary List Chunks
// @Description Retrieve paginated chunks for a document with optional filtering.
// @Tags chunks
// @Param request body service.ListChunksRequest true "List chunks parameters"
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/chunk/list [post]
func (h *ChunkHandler) List(c *gin.Context) {
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
common.ErrorWithCode(c, errorCode, errorMessage)
return
}
// Bind JSON request
var req service.ListChunksRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 400, nil, err.Error())
return
}
// Set default values for optional parameters
if req.Page == nil {
defaultPage := 1
req.Page = &defaultPage
}
if req.Size == nil {
defaultSize := 30
req.Size = &defaultSize
}
ctx := c.Request.Context()
resp, err := h.chunkService.List(ctx, &req, user.ID)
if err != nil {
if respondPermissionErrorIf(c, err, false) {
return
}
common.ResponseWithHttpCodeData(c, http.StatusInternalServerError, 500, nil, err.Error())
return
}
common.SuccessWithData(c, resp, "success")
}
// SwitchChunks enable or disable a chunk
func (h *ChunkHandler) SwitchChunks(c *gin.Context) {
user, errorCode, errorMessage := GetUser(c)
@@ -539,7 +495,7 @@ func optionalBodyString(rawBody map[string]interface{}, field, typeMessage strin
// @Tags chunks
// @Param request body service.UpdateChunkRequest true "update chunk"
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/chunk/update [post]
// @Router /api/v1/datasets/{dataset_id}/documents/{document_id}/chunks/{chunk_id} [patch]
func (h *ChunkHandler) UpdateChunk(c *gin.Context) {
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
@@ -547,7 +503,7 @@ func (h *ChunkHandler) UpdateChunk(c *gin.Context) {
return
}
// Validate allowed update fields and get IDs from body
// Decode the update fields.
body, err := io.ReadAll(c.Request.Body)
var rawBody map[string]interface{}
if err == nil {
-221
View File
@@ -66,9 +66,6 @@ type documentServiceIface interface {
GetDocumentImageForDocument(ctx context.Context, userID, docID, imageID string) ([]byte, error)
GetDocumentThumbnail(ctx context.Context, userID, docID string) ([]byte, error)
GetMetadataSummary(ctx context.Context, kbID string, docIDs []string) (map[string]interface{}, error)
SetDocumentMetadata(ctx context.Context, docID string, meta map[string]interface{}) error
DeleteDocumentMetadata(ctx context.Context, docID string, keys []string) error
DeleteDocumentAllMetadata(ctx context.Context, docID string) error
GetDocumentMetadataByID(ctx context.Context, docID string) (map[string]interface{}, error)
GetDocumentArtifact(ctx context.Context, filename, userID string) (*document.ArtifactResponse, error)
GetDocumentPreview(ctx context.Context, userID, docID string) (*document.DocumentPreview, error)
@@ -1311,137 +1308,6 @@ func stringValue(value *string) string {
return *value
}
// MetadataSummary handles the metadata summary request
func (h *DocumentHandler) MetadataSummary(c *gin.Context) {
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
common.ErrorWithCode(c, errorCode, errorMessage)
return
}
var requestBody struct {
KBID string `json:"kb_id" binding:"required"`
DocIDs []string `json:"doc_ids"`
}
if err := c.ShouldBindJSON(&requestBody); err != nil {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, "kb_id is required")
return
}
kbID := requestBody.KBID
if kbID == "" {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, "kb_id is required")
return
}
ctx := c.Request.Context()
if err := h.datasetService.CheckAccess(ctx, permission.Subject{UserID: user.ID}, kbID, permission.OperationRead); err != nil {
respondHTTPPermissionError(c, err, false)
return
}
summary, err := h.documentService.GetMetadataSummary(ctx, kbID, requestBody.DocIDs)
if err != nil {
common.ResponseWithHttpCodeData(c, http.StatusInternalServerError, 1, nil, "Failed to get metadata summary: "+err.Error())
return
}
common.SuccessWithData(c, gin.H{"summary": summary}, "success")
}
// SetMetaRequest represents the request for setting document metadata
type SetMetaRequest struct {
DocID string `json:"doc_id" binding:"required"`
Meta string `json:"meta" binding:"required"`
}
// SetMeta handles the set metadata request for a document
// @Summary Set Document Metadata
// @Description Set metadata for a specific document
// @Tags documents
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param request body SetMetaRequest true "metadata info"
// @Success 200 {object} map[string]interface{}
// @Router /v1/document/set_meta [post]
func (h *DocumentHandler) SetMeta(c *gin.Context) {
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
common.ErrorWithCode(c, errorCode, errorMessage)
return
}
var req SetMetaRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, err.Error())
return
}
if req.DocID == "" {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, "doc_id is required")
return
}
// Parse meta JSON string
var meta map[string]interface{}
if err := json.Unmarshal([]byte(req.Meta), &meta); err != nil {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, "Json syntax error: "+err.Error())
return
}
if meta == nil {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, "meta is required")
return
}
// Validate meta values - must be str, int, float, or list of those
for k, v := range meta {
switch val := v.(type) {
case string, int, float64:
// Valid
case []interface{}:
for _, item := range val {
if _, ok := item.(string); !ok {
if _, ok = item.(float64); !ok {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, fmt.Sprintf("Unsupported type in list for key %s: %T", k, item))
return
}
}
}
default:
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, fmt.Sprintf("Unsupported type for key %s: %T", k, v))
return
}
}
// Authorization: user must be able to access the document's dataset.
ctx := c.Request.Context()
doc, err := h.documentService.GetDocumentByID(ctx, req.DocID)
if err != nil {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, "document not found")
return
}
if err := h.datasetService.CheckAccess(ctx, permission.Subject{UserID: user.ID}, doc.KbID, permission.OperationUpdate); err != nil {
respondPermissionError(c, err, false)
return
}
err = h.documentService.SetDocumentMetadata(ctx, req.DocID, meta)
if err != nil {
errMsg := err.Error()
if strings.Contains(errMsg, "no such document") || strings.Contains(errMsg, "document not found") {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, errMsg)
} else {
common.ResponseWithHttpCodeData(c, http.StatusInternalServerError, 1, nil, "Failed to set metadata: "+errMsg)
}
return
}
common.SuccessWithData(c, true, "success")
}
// Ingest handles document ingestion
// @Summary Ingest Document
// @Description Ingest a document for processing
@@ -1477,93 +1343,6 @@ func (h *DocumentHandler) Ingest(c *gin.Context) {
common.SuccessWithData(c, true, "success")
}
// DeleteMetaRequest represents the request for deleting document metadata
type DeleteMetaRequest struct {
DocID string `json:"doc_id" binding:"required"`
Keys string `json:"keys"` // optional - if provided, deletes specific keys; otherwise deletes entire document metadata
}
// DeleteMeta handles the delete metadata request for a document
// If Keys is provided, deletes specific metadata keys; otherwise deletes entire document metadata
// @Summary Delete Document Metadata
// @Description Delete metadata keys or entire document metadata for a specific document
// @Tags documents
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param request body DeleteMetaRequest true "metadata keys to delete or empty to delete all"
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/document/delete_meta [post]
func (h *DocumentHandler) DeleteMeta(c *gin.Context) {
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
common.ErrorWithCode(c, errorCode, errorMessage)
return
}
var req DeleteMetaRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, err.Error())
return
}
if req.DocID == "" {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, "doc_id is required")
return
}
ctx := c.Request.Context()
doc, err := h.documentService.GetDocumentByID(ctx, req.DocID)
if err != nil {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, "document not found")
return
}
if err := h.datasetService.CheckAccess(ctx, permission.Subject{UserID: user.ID}, doc.KbID, permission.OperationUpdate); err != nil {
respondPermissionError(c, err, false)
return
}
// If Keys is provided, parse and delete specific keys; otherwise delete entire document metadata
if req.Keys != "" {
// Parse keys JSON string - expected to be a list of key names to delete
var keys []string
if err = json.Unmarshal([]byte(req.Keys), &keys); err != nil {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, "Json syntax error: "+err.Error())
return
}
if keys == nil || len(keys) == 0 {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, "keys list is required")
return
}
err = h.documentService.DeleteDocumentMetadata(ctx, req.DocID, keys)
if err != nil {
errMsg := err.Error()
if strings.Contains(errMsg, "no such document") || strings.Contains(errMsg, "document not found") {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, errMsg)
} else {
common.ResponseWithHttpCodeData(c, http.StatusInternalServerError, 1, nil, "Failed to delete metadata: "+errMsg)
}
return
}
} else {
// Delete entire document metadata
err = h.documentService.DeleteDocumentAllMetadata(ctx, req.DocID)
if err != nil {
errMsg := err.Error()
if strings.Contains(errMsg, "no such document") || strings.Contains(errMsg, "document not found") {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 1, nil, errMsg)
} else {
common.ResponseWithHttpCodeData(c, http.StatusInternalServerError, 1, nil, "Failed to delete metadata: "+errMsg)
}
return
}
}
common.SuccessWithData(c, true, "success")
}
type ListIngestionsRequest struct {
DatasetID *string `json:"dataset_id"`
}
+5 -86
View File
@@ -146,9 +146,6 @@ type fakeDocumentService struct {
metadataErr error
metadataKBID string
metadataDocIDs []string
setMetaCalled bool
setMetaDocID string
setMetaValue map[string]interface{}
uploadLocalData []map[string]interface{}
uploadLocalErrs []string
uploadLocalKB *entity.Knowledgebase
@@ -336,18 +333,6 @@ func (f *fakeDocumentService) HasActiveIngestionTasks(ctx context.Context, datas
return f.hasActiveTasks, f.hasActiveTasksErr
}
func (f *fakeDocumentService) SetDocumentMetadata(ctx context.Context, docID string, meta map[string]interface{}) error {
f.setMetaCalled = true
f.setMetaDocID = docID
f.setMetaValue = meta
return nil
}
func (f *fakeDocumentService) DeleteDocumentMetadata(ctx context.Context, docID string, keys []string) error {
return nil
}
func (f *fakeDocumentService) DeleteDocumentAllMetadata(ctx context.Context, docID string) error {
return nil
}
func (f *fakeDocumentService) GetDocumentMetadataByID(ctx context.Context, docID string) (map[string]interface{}, error) {
return nil, nil
}
@@ -457,17 +442,18 @@ func TestDocumentReadHandlersForbidden(t *testing.T) {
body string
params gin.Params
handler gin.HandlerFunc
status int
}{
{"document detail", http.MethodGet, "/api/v1/documents/doc-1", "", gin.Params{{Key: "id", Value: "doc-1"}}, h.GetDocumentByID},
{"metadata summary", http.MethodPost, "/api/v1/document/metadata/summary", `{"kb_id":"kb-owner","doc_ids":["doc-1"]}`, nil, h.MetadataSummary},
{"document detail", http.MethodGet, "/api/v1/documents/doc-1", "", gin.Params{{Key: "id", Value: "doc-1"}}, h.GetDocumentByID, http.StatusForbidden},
{"metadata summary", http.MethodGet, "/api/v1/datasets/kb-owner/metadata/summary?doc_ids=doc-1", "", gin.Params{{Key: "dataset_id", Value: "kb-owner"}}, h.MetadataSummaryByDataset, http.StatusOK},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c, w := setupGinContextWithUser(tt.method, tt.path, tt.body)
c.Params = tt.params
tt.handler(c)
if w.Code != http.StatusForbidden {
t.Fatalf("status = %d, want 403: %s", w.Code, w.Body.String())
if w.Code != tt.status {
t.Fatalf("status = %d, want %d: %s", w.Code, tt.status, w.Body.String())
}
var resp struct {
Code common.ErrorCode `json:"code"`
@@ -487,73 +473,6 @@ func TestDocumentReadHandlersForbidden(t *testing.T) {
}
}
func TestSetMetaHandler_NotAccessible(t *testing.T) {
setupDocumentPermissionDB(t, false)
fake := &fakeDocumentService{
doc: &document.DocumentResponse{ID: "doc-1", KbID: "kb-owner"},
}
h := &DocumentHandler{
documentService: fake,
datasetService: dataset.NewDatasetService(),
}
c, w := setupGinContextWithUser("POST", "/api/v1/document/set_meta", `{"doc_id":"doc-1","meta":"{\"poc\":\"blocked\"}"}`)
h.SetMeta(c)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
}
var resp map[string]interface{}
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
t.Fatal(err)
}
if resp["code"] != float64(common.CodeForbidden) {
t.Fatalf("expected auth error, got %v", resp)
}
if resp["message"] != "Permission denied" {
t.Fatalf("unexpected message: %v", resp["message"])
}
if fake.setMetaCalled {
t.Fatal("SetDocumentMetadata should not be called without dataset access")
}
}
func TestSetMetaHandler_Accessible(t *testing.T) {
setupDocumentPermissionDB(t, true)
fake := &fakeDocumentService{
doc: &document.DocumentResponse{ID: "doc-1", KbID: "kb-owner"},
}
h := &DocumentHandler{
documentService: fake,
datasetService: dataset.NewDatasetService(),
}
c, w := setupGinContextWithUser("POST", "/api/v1/document/set_meta", `{"doc_id":"doc-1","meta":"{\"category\":\"tech\",\"year\":2026}"}`)
h.SetMeta(c)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
}
var resp map[string]interface{}
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
t.Fatal(err)
}
if resp["code"] != float64(common.CodeSuccess) || resp["data"] != true {
t.Fatalf("unexpected response: %v", resp)
}
if !fake.setMetaCalled {
t.Fatal("SetDocumentMetadata should be called with dataset access")
}
if fake.setMetaDocID != "doc-1" {
t.Fatalf("set meta doc id = %q, want doc-1", fake.setMetaDocID)
}
if fake.setMetaValue["category"] != "tech" || fake.setMetaValue["year"] != float64(2026) {
t.Fatalf("unexpected meta: %#v", fake.setMetaValue)
}
}
func TestDeleteDocumentHandler_NotAccessible(t *testing.T) {
setupDocumentPermissionDB(t, false)
+1 -63
View File
@@ -133,33 +133,6 @@ func (h *FileHandler) ListFiles(c *gin.Context) {
common.SuccessWithData(c, result, "success")
}
// GetRootFolder gets root folder for current user
// @Summary Get Root Folder
// @Description Get or create root folder for the current user
// @Tags file
// @Accept json
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Router /v1/file/root_folder [get]
func (h *FileHandler) GetRootFolder(c *gin.Context) {
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
common.ErrorWithCode(c, errorCode, errorMessage)
return
}
userID := user.ID
ctx := c.Request.Context()
// Get root folder
rootFolder, err := h.fileService.GetRootFolder(ctx, userID)
if err != nil {
respondFileServiceError(c, err)
return
}
common.SuccessWithData(c, gin.H{"root_folder": rootFolder}, common.CodeSuccess.Message())
}
// GetParentFolder gets parent folder of a file
// @Summary Get Parent Folder
// @Description Get parent folder of a file by file ID
@@ -168,7 +141,7 @@ func (h *FileHandler) GetRootFolder(c *gin.Context) {
// @Produce json
// @Param file_id query string true "file ID"
// @Success 200 {object} map[string]interface{}
// @Router /v1/file/parent_folder [get]
// @Router /api/v1/files/{id}/parent [get]
func (h *FileHandler) GetParentFolder(c *gin.Context) {
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
@@ -195,41 +168,6 @@ func (h *FileHandler) GetParentFolder(c *gin.Context) {
common.SuccessWithData(c, gin.H{"parent_folder": parentFolder}, common.CodeSuccess.Message())
}
// GetAllParentFolders gets all parent folders in path
// @Summary Get All Parent Folders
// @Description Get all parent folders in path from file to root
// @Tags file
// @Accept json
// @Produce json
// @Param file_id query string true "file ID"
// @Success 200 {object} map[string]interface{}
// @Router /v1/file/all_parent_folder [get]
func (h *FileHandler) GetAllParentFolders(c *gin.Context) {
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
common.ErrorWithCode(c, errorCode, errorMessage)
return
}
userID := user.ID
// Get file_id from query
fileID := c.Query("file_id")
if fileID == "" {
common.ResponseWithCodeData(c, common.CodeBadRequest, nil, "file_id is required")
return
}
ctx := c.Request.Context()
// Get all parent folders with permission check
parentFolders, err := h.fileService.GetAllParentFolders(ctx, userID, fileID)
if err != nil {
respondFileServiceError(c, err)
return
}
common.SuccessWithData(c, gin.H{"parent_folders": parentFolders}, common.CodeSuccess.Message())
}
// GetFileAncestors gets all ancestor folders of a file (matches Python /files/<file_id>/ancestors)
// @Summary Get File Ancestors
// @Description Get all ancestor folders in path from file to root
+1 -1
View File
@@ -160,7 +160,7 @@ func (h *TenantHandler) TenantInfo(c *gin.Context) {
// @Tags tenants
// @Security ApiKeyAuth
// @Success 200 {object} map[string]interface{}
// @Router /v1/tenant/list [get]
// @Router /api/v1/tenants [get]
func (h *TenantHandler) TenantList(c *gin.Context) {
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
+12 -33
View File
@@ -281,7 +281,6 @@ func (r *Router) Setup(engine *gin.Engine) {
// Tenant routes (per-tenant resources)
tenant := v1.Group("/tenant")
{
tenant.GET("/list", r.tenantHandler.TenantList)
tenant.POST("/chunk_store", r.tenantHandler.CreateChunkStore) // Internal API only for GO
tenant.DELETE("/chunk_store", r.tenantHandler.DeleteChunkStore) // Internal API only for GO
tenant.POST("/metadata_store", r.tenantHandler.CreateMetadataStore) // Internal API only for GO
@@ -420,7 +419,6 @@ func (r *Router) Setup(engine *gin.Engine) {
searches.GET("/:search_id", r.searchHandler.GetSearch)
searches.PUT("/:search_id", r.searchHandler.UpdateSearch)
searches.DELETE("/:search_id", r.searchHandler.DeleteSearch)
searches.POST("/:search_id/completion", r.searchHandler.Completion)
searches.POST("/:search_id/completions", r.searchHandler.Completion)
}
@@ -441,14 +439,6 @@ func (r *Router) Setup(engine *gin.Engine) {
workspaceFiles.GET("/:file_id/versions", r.fileCommitHandler.GetFileVersionHistory)
}
// File routes
file := authorized.Group("/v1/file")
{
file.GET("/root_folder", r.fileHandler.GetRootFolder)
file.GET("/parent_folder", r.fileHandler.GetParentFolder)
file.GET("/all_parent_folder", r.fileHandler.GetAllParentFolders)
}
// File commit routes — /folders/ takes folder_id directly
commitFolders := v1.Group("/folders")
{
@@ -556,13 +546,18 @@ func (r *Router) Setup(engine *gin.Engine) {
provider.PATCH("/:provider_id_or_name/instances/:instance_id_or_name/models/*model_name", r.providerHandler.AlterModel)
provider.POST("/:provider_id_or_name/instances/:instance_id_or_name/models", r.providerHandler.AddModel)
provider.DELETE("/:provider_id_or_name/instances/:instance_id_or_name/models", r.providerHandler.DropInstanceModels)
v1.POST("/chat/to_model", r.providerHandler.ChatToModel)
v1.POST("/embeddings", r.providerHandler.EmbedText)
v1.POST("/rerank", r.providerHandler.RerankDocument)
v1.POST("/audio/transcriptions", r.providerHandler.TranscribeAudio)
v1.POST("/audio/speech", r.providerHandler.AudioSpeech)
v1.POST("/file/ocr", r.providerHandler.OCRFile)
v1.POST("/file/parse", r.providerHandler.ParseFile)
}
// command is for CLI
command := v1
{
command.POST("/chat/to_model", r.providerHandler.ChatToModel)
command.POST("/embeddings", r.providerHandler.EmbedText)
command.POST("/rerank", r.providerHandler.RerankDocument)
command.POST("/audio/transcriptions", r.providerHandler.TranscribeAudio)
command.POST("/audio/speech", r.providerHandler.AudioSpeech)
command.POST("/file/ocr", r.providerHandler.OCRFile)
command.POST("/file/parse", r.providerHandler.ParseFile)
}
model := v1.Group("/models")
@@ -692,22 +687,6 @@ func (r *Router) Setup(engine *gin.Engine) {
system.PUT("/concurrency", r.systemHandler.SetConcurrency)
}
// Document routes
doc := v1.Group("/document")
{
doc.POST("/list", r.documentHandler.ListDocuments)
doc.POST("/metadata/summary", r.documentHandler.MetadataSummary)
doc.POST("/set_meta", r.documentHandler.SetMeta)
doc.POST("/delete_meta", r.documentHandler.DeleteMeta) // Internal API only for GO
}
// Chunk routes
chunk := v1.Group("/chunk")
{
chunk.POST("/list", r.chunkHandler.List)
chunk.POST("/update", r.chunkHandler.UpdateChunk) // Internal API only for GO
}
// Chat Channel
chatChannel := v1.Group("/chat-channels")
{
+34
View File
@@ -7,12 +7,46 @@ import (
"testing"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"ragflow/internal/common"
"ragflow/internal/handler"
"ragflow/internal/service"
)
func TestRouterSetupRemovesActionRoutes(t *testing.T) {
gin.SetMode(gin.TestMode)
originalLogger := common.Logger
common.Logger = zap.NewNop()
t.Cleanup(func() { common.Logger = originalLogger })
engine := gin.New()
r := &Router{authHandler: handler.NewAuthHandler()}
r.Setup(engine)
tests := []struct {
method string
path string
}{
{http.MethodGet, "/api/v1/tenant/list"},
{http.MethodPost, "/api/v1/document/list"},
{http.MethodPost, "/api/v1/document/metadata/summary"},
{http.MethodPost, "/api/v1/document/delete_meta"},
{http.MethodPost, "/api/v1/document/set_meta"},
{http.MethodPost, "/api/v1/chunk/list"},
{http.MethodPost, "/api/v1/chunk/update"},
}
for _, tt := range tests {
t.Run(tt.method+" "+tt.path, func(t *testing.T) {
resp := httptest.NewRecorder()
engine.ServeHTTP(resp, httptest.NewRequest(tt.method, tt.path, nil))
if resp.Code != http.StatusNotFound {
t.Fatalf("status=%d body=%s; want 404", resp.Code, resp.Body.String())
}
})
}
}
func TestConnectorRoutesDoNotConflictWithOAuthCallbacks(t *testing.T) {
gin.SetMode(gin.TestMode)
+3 -3
View File
@@ -107,7 +107,7 @@ def test_search_completion_requires_question(rest_client, search_resource):
search_id = search_resource
expected_message = "question is required"
completion_res = rest_client.post(f"/searches/{search_id}/completion", json={})
completion_res = rest_client.post(f"/searches/{search_id}/completions", json={})
assert completion_res.status_code == 200
completion_payload = completion_res.json()
assert completion_payload["code"] == 101, completion_payload
@@ -123,7 +123,7 @@ def test_search_completion_requires_question(rest_client, search_resource):
@pytest.mark.p2
def test_search_completion_requires_kb_ids(rest_client, search_resource):
search_id = search_resource
for path in ("completion", "completions"):
for path in ["completions"]:
res = rest_client.post(
f"/searches/{search_id}/{path}",
json={"question": "what is coriander?"},
@@ -140,7 +140,7 @@ def test_search_completion_sse_shape_when_kb_ids_provided(rest_client, search_re
# Even with kb_ids provided, runtime may return an error event in-stream, but
# contract remains SSE with JSON data lines and terminal boolean event.
res = rest_client.post(
f"/searches/{search_id}/completion",
f"/searches/{search_id}/completions",
json={"question": "what is coriander?", "kb_ids": ["nonexistent_dataset"]},
timeout=60,
)
-1
View File
@@ -22,7 +22,6 @@ export const enum DocumentApiAction {
RemoveDocument = 'removeDocument',
SaveDocumentName = 'saveDocumentName',
SetDocumentParser = 'setDocumentParser',
SetDocumentMeta = 'setDocumentMeta',
FetchDocumentFilter = 'fetchDocumentFilter',
CreateDocument = 'createDocument',
FetchDocumentThumbnails = 'fetchDocumentThumbnails',
+1 -37
View File
@@ -29,10 +29,7 @@ import {
IClaimsResponse,
IStructureGraphResponse,
} from '@/interfaces/database/document-structure';
import {
IChangeParserConfigRequestBody,
IDocumentMetaRequestBody,
} from '@/interfaces/request/document';
import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
import i18n from '@/locales/config';
import { EMPTY_METADATA_FIELD } from '@/pages/dataset/dataset/use-select-filters';
import {
@@ -702,39 +699,6 @@ export const useSetDocumentPipelineParser = () => {
return { setDocumentPipelineParser: mutateAsync, data, loading };
};
export const useSetDocumentMeta = () => {
const queryClient = useQueryClient();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [DocumentApiAction.SetDocumentMeta],
mutationFn: async (params: IDocumentMetaRequestBody) => {
try {
const { data } = await kbService.setMeta({
meta: params.meta,
doc_id: params.documentId,
});
if (data?.code === 0) {
queryClient.invalidateQueries({
queryKey: DocumentKeys.all(),
});
message.success(i18n.t('message.modified'));
}
return data?.code;
} catch (error) {
message.error('error:' + error);
}
},
});
return { setDocumentMeta: mutateAsync, data, loading };
};
export const useCreateDocument = () => {
const { id } = useParams();
const { setPaginationParams, page } = useSetPaginationParams();
-5
View File
@@ -44,8 +44,3 @@ export interface IChangeParserRequestBody {
doc_id?: string;
parser_config: IChangeParserConfigRequestBody;
}
export interface IDocumentMetaRequestBody {
documentId: string;
meta: string; // json format string
}
@@ -307,8 +307,6 @@ export const useManageMetaDataModal = (
resetOperations,
} = useMetadataOperations();
// const { setDocumentMeta } = useSetDocumentMeta();
useEffect(() => {
if (fetchTypeList.includes(type)) {
if (data) {
@@ -1,128 +0,0 @@
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { TagRenameId } from '@/constants/knowledge';
import { IModalProps } from '@/interfaces/common';
import { useTranslation } from 'react-i18next';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { ButtonLoading } from '@/components/ui/button';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { IDocumentInfo } from '@/interfaces/database/document';
import Editor, { loader } from '@monaco-editor/react';
import DOMPurify from 'dompurify';
import { useEffect } from 'react';
loader.config({ paths: { vs: '/vs' } });
export function SetMetaDialog({
hideModal,
onOk,
loading,
initialMetaData,
}: IModalProps<any> & { initialMetaData?: IDocumentInfo['meta_fields'] }) {
const { t } = useTranslation();
const FormSchema = z.object({
meta: z
.string()
.min(1, {
message: t('knowledgeDetails.pleaseInputJson'),
})
.trim()
.refine(
(value) => {
try {
JSON.parse(value);
return true;
} catch (error) {
return false;
}
},
{ message: t('knowledgeDetails.pleaseInputJson') },
),
});
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {},
});
async function onSubmit(data: z.infer<typeof FormSchema>) {
const ret = await onOk?.(data.meta);
if (ret) {
hideModal?.();
}
}
useEffect(() => {
form.setValue('meta', JSON.stringify(initialMetaData, null, 4));
}, [form, initialMetaData]);
return (
<Dialog open onOpenChange={hideModal}>
<DialogContent>
<DialogHeader>
<DialogTitle>{t('knowledgeDetails.setMetaData')}</DialogTitle>
</DialogHeader>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6"
id={TagRenameId}
>
<FormField
control={form.control}
name="meta"
render={({ field }) => (
<FormItem>
<FormLabel
tooltip={
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(
t('knowledgeDetails.documentMetaTips'),
),
}}
></div>
}
>
{t('knowledgeDetails.metaData')}
</FormLabel>
<FormControl>
<Editor
height={200}
defaultLanguage="json"
theme="vs-dark"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
<DialogFooter>
<ButtonLoading type="submit" form={TagRenameId} loading={loading}>
{t('common.save')}
</ButtonLoading>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
@@ -1,50 +0,0 @@
import { useSetModalState } from '@/hooks/common-hooks';
import { useSetDocumentMeta } from '@/hooks/use-document-request';
import { IDocumentInfo } from '@/interfaces/database/document';
import { useCallback, useState } from 'react';
export const useSaveMeta = () => {
const { setDocumentMeta, loading } = useSetDocumentMeta();
const [record, setRecord] = useState<IDocumentInfo>({} as IDocumentInfo);
const {
visible: setMetaVisible,
hideModal: hideSetMetaModal,
showModal: showSetMetaModal,
} = useSetModalState();
const onSetMetaModalOk = useCallback(
async (meta: string) => {
const ret = await setDocumentMeta({
documentId: record?.id,
meta,
});
if (ret === 0) {
hideSetMetaModal();
}
},
[setDocumentMeta, record?.id, hideSetMetaModal],
);
const handleShowSetMetaModal = useCallback(
(row: IDocumentInfo) => {
setRecord(row);
showSetMetaModal();
},
[showSetMetaModal],
);
return {
setMetaLoading: loading,
onSetMetaModalOk,
setMetaVisible,
hideSetMetaModal,
showSetMetaModal: handleShowSetMetaModal,
metaRecord: record,
};
};
export type UseSaveMetaShowType = Pick<
ReturnType<typeof useSaveMeta>,
'showSetMetaModal'
>;
-5
View File
@@ -37,7 +37,6 @@ const {
documentIngest,
listTagByKnowledgeIds,
listPipelines,
setMeta,
getMeta,
getMetaKeys,
retrievalTestShare,
@@ -64,10 +63,6 @@ const methods = {
url: documentThumbnails,
method: 'get',
},
setMeta: {
url: setMeta,
method: 'post',
},
listTagByKnowledgeIds: {
url: listTagByKnowledgeIds,
method: 'get',
-1
View File
@@ -289,7 +289,6 @@ export default {
webCrawl: (datasetId: string) =>
`${restAPIv1}/datasets/${datasetId}/documents?type=web`,
documentInfoUpload: `${restAPIv1}/documents/upload`,
setMeta: `${webAPI}/document/set_meta`,
getDatasetFilter: (datasetId: string) =>
`${restAPIv1}/datasets/${datasetId}/documents?type=filter`,