Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 184 additions & 2 deletions internal/handler/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,17 @@ import (
"errors"
"fmt"
"mime"
"mime/multipart"
"net/http"
"path/filepath"
"ragflow/internal/common"
"ragflow/internal/entity"
"strconv"
"strings"
"time"

"github.com/gin-gonic/gin"

"ragflow/internal/common"
"ragflow/internal/entity"
"ragflow/internal/service"
)

Expand All @@ -55,19 +56,22 @@ type documentServiceIface interface {
DeleteDocumentMetadata(docID string, keys []string) error
DeleteDocumentAllMetadata(docID string) error
GetDocumentMetadataByID(docID string) (map[string]interface{}, error)
BatchUpdateDocumentMetadata(datasetID string, req *service.BatchUpdateMetadataRequest) (*service.BatchUpdateMetadataResult, error)
}

// DocumentHandler document handler
type DocumentHandler struct {
documentService documentServiceIface
datasetService *service.DatasetService
fileService *service.FileService
}

// NewDocumentHandler create document handler
func NewDocumentHandler(documentService *service.DocumentService, datasetService *service.DatasetService) *DocumentHandler {
return &DocumentHandler{
documentService: documentService,
datasetService: datasetService,
fileService: service.NewFileService(),
}
}

Expand Down Expand Up @@ -886,3 +890,181 @@ func (h *DocumentHandler) StopParseDocuments(c *gin.Context) {
"data": result,
})
}

// UploadInfo uploads one or more files (or a URL) and returns file metadata.
// Mirrors Python POST /api/v1/documents/upload (upload_info).
// @Summary Upload document info
// @Description Upload files via multipart form or supply ?url=... to fetch remotely.
// @Tags documents
// @Accept multipart/form-data
// @Produce json
// @Param file formData file false "File(s) to upload"
// @Param url query string false "Remote URL to fetch"
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/documents/upload [post]
func (h *DocumentHandler) UploadInfo(c *gin.Context) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here

user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
jsonError(c, errorCode, errorMessage)
return
}

url := c.Query("url")

form, _ := c.MultipartForm()
var files []*multipart.FileHeader
if form != nil && form.File != nil {
files = form.File["file"]
}

if len(files) > 0 && url != "" {
c.JSON(http.StatusOK, gin.H{"code": common.CodeArgumentError, "data": false,
"message": "Provide either multipart file(s) or ?url=..., not both."})
return
}
if len(files) == 0 && url == "" {
c.JSON(http.StatusOK, gin.H{"code": common.CodeArgumentError, "data": false,
"message": "Missing input: provide multipart file(s) or url"})
return
}

if url != "" {
// URL upload path — delegate to file service.
data, err := h.fileService.UploadFromURL(user.ID, url)
if err != nil {
c.JSON(http.StatusOK, gin.H{"code": common.CodeDataError, "data": false, "message": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"code": common.CodeSuccess, "data": data, "message": "success"})
return
}

// Multipart file(s).
uploaded, err := h.fileService.UploadFile(user.ID, "", files)
if err != nil {
c.JSON(http.StatusOK, gin.H{"code": common.CodeDataError, "data": false, "message": err.Error()})
return
}

if len(files) == 1 {
if len(uploaded) > 0 {
c.JSON(http.StatusOK, gin.H{"code": common.CodeSuccess, "data": uploaded[0], "message": "success"})
} else {
c.JSON(http.StatusOK, gin.H{"code": common.CodeDataError, "data": false, "message": "upload failed"})
}
return
}
c.JSON(http.StatusOK, gin.H{"code": common.CodeSuccess, "data": uploaded, "message": "success"})
}

// batchMetadataUpdate is the shared implementation for both bulk-metadata endpoints.
func (h *DocumentHandler) batchMetadataUpdate(c *gin.Context) {
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
jsonError(c, errorCode, errorMessage)
return
}

datasetID := c.Param("dataset_id")
if datasetID == "" {
c.JSON(http.StatusOK, gin.H{"code": common.CodeDataError, "data": false, "message": "dataset_id is required"})
return
}

if !h.datasetService.Accessible(datasetID, user.ID) {
c.JSON(http.StatusOK, gin.H{"code": common.CodeDataError, "data": false,
"message": fmt.Sprintf("You don't own the dataset %s.", datasetID)})
return
}

var body struct {
Selector map[string]interface{} `json:"selector"`
Updates []map[string]interface{} `json:"updates"`
Deletes []map[string]interface{} `json:"deletes"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusOK, gin.H{"code": common.CodeDataError, "data": false, "message": err.Error()})
return
}

// Validate updates.
updates := make([]service.MetadataUpdate, 0, len(body.Updates))
for _, u := range body.Updates {
key, hasKey := u["key"].(string)
_, hasVal := u["value"]
if !hasKey || key == "" || !hasVal {
c.JSON(http.StatusOK, gin.H{"code": common.CodeDataError, "data": false, "message": "Each update requires key and value."})
return
}
updates = append(updates, service.MetadataUpdate{Key: key, Value: u["value"]})
}

// Validate deletes.
deletes := make([]service.MetadataDelete, 0, len(body.Deletes))
for _, d := range body.Deletes {
key, ok := d["key"].(string)
if !ok || key == "" {
c.JSON(http.StatusOK, gin.H{"code": common.CodeDataError, "data": false, "message": "Each delete requires key."})
return
}
deletes = append(deletes, service.MetadataDelete{Key: key})
}

// Build selector.
selector := service.MetadataSelector{}
if body.Selector != nil {
if ids, ok := body.Selector["document_ids"].([]interface{}); ok {
for _, id := range ids {
if s, ok := id.(string); ok {
selector.DocumentIDs = append(selector.DocumentIDs, s)
}
}
}
if mc, ok := body.Selector["metadata_condition"].(map[string]interface{}); ok {
selector.MetadataCondition = mc
}
}

req := &service.BatchUpdateMetadataRequest{
Selector: selector,
Updates: updates,
Deletes: deletes,
}

result, err := h.documentService.BatchUpdateDocumentMetadata(datasetID, req)
if err != nil {
c.JSON(http.StatusOK, gin.H{"code": common.CodeDataError, "data": false, "message": err.Error()})
return
}

c.JSON(http.StatusOK, gin.H{"code": common.CodeSuccess, "data": result, "message": "success"})
}

// UpdateDocumentMetadatas handles PATCH /api/v1/datasets/:dataset_id/documents/metadatas.
// Mirrors Python PATCH /datasets/<dataset_id>/documents/metadatas (update_metadata).
// @Summary Bulk update document metadata
// @Description Bulk-set or bulk-delete metadata fields on documents in a dataset.
// @Tags documents
// @Accept json
// @Produce json
// @Param dataset_id path string true "Dataset ID"
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/datasets/{dataset_id}/documents/metadatas [patch]
func (h *DocumentHandler) UpdateDocumentMetadatas(c *gin.Context) {
h.batchMetadataUpdate(c)
}

// MetadataBatchUpdate handles POST /api/v1/datasets/:dataset_id/metadata/update.
// Mirrors Python POST /datasets/<dataset_id>/metadata/update (metadata_batch_update).
// @Summary Batch update document metadata (POST variant)
// @Description Batch-apply updates and deletes to document metadata via selector.
// @Tags documents
// @Accept json
// @Produce json
// @Param dataset_id path string true "Dataset ID"
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/datasets/{dataset_id}/metadata/update [post]
func (h *DocumentHandler) MetadataBatchUpdate(c *gin.Context) {
h.batchMetadataUpdate(c)
}

3 changes: 3 additions & 0 deletions internal/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ func (r *Router) Setup(engine *gin.Engine) {
documents := v1.Group("/documents")
{
documents.POST("", r.documentHandler.CreateDocument)
documents.POST("/upload", r.documentHandler.UploadInfo)
documents.GET("", r.documentHandler.ListDocuments)
documents.GET("/:id", r.documentHandler.GetDocumentByID)
documents.PUT("/:id", r.documentHandler.UpdateDocument)
Expand Down Expand Up @@ -241,6 +242,8 @@ func (r *Router) Setup(engine *gin.Engine) {
// Metadata Config
datasets.GET("/:dataset_id/metadata/config", r.datasetsHandler.GetMetadataConfig)
datasets.PUT("/:dataset_id/metadata/config", r.datasetsHandler.UpdateMetadataConfig)
datasets.POST("/:dataset_id/metadata/update", r.documentHandler.MetadataBatchUpdate)
datasets.PATCH("/:dataset_id/documents/metadatas", r.documentHandler.UpdateDocumentMetadatas)

// Dataset documents
datasets.GET("/:dataset_id/documents", r.documentHandler.ListDocuments)
Expand Down
Loading