2026-03-04 19:17:16 +08:00
//
// 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 handler
import (
2026-07-02 16:05:49 +08:00
"bytes"
2026-04-07 09:44:51 +08:00
"encoding/json"
2026-06-01 11:22:08 +08:00
"errors"
2026-04-07 09:44:51 +08:00
"fmt"
2026-06-25 13:36:49 +08:00
"io"
2026-06-01 11:22:08 +08:00
"mime"
2026-06-24 14:52:47 +08:00
"mime/multipart"
2026-03-04 19:17:16 +08:00
"net/http"
2026-06-25 13:36:49 +08:00
"net/url"
2026-06-01 11:22:08 +08:00
"path/filepath"
2026-03-11 11:23:13 +08:00
"ragflow/internal/common"
2026-05-18 20:00:11 +08:00
"ragflow/internal/entity"
2026-06-08 11:37:06 +08:00
"ragflow/internal/utility"
2026-03-04 19:17:16 +08:00
"strconv"
2026-04-09 09:52:31 +08:00
"strings"
2026-05-18 20:00:11 +08:00
"time"
2026-03-04 19:17:16 +08:00
"github.com/gin-gonic/gin"
2026-07-02 15:57:07 +08:00
"ragflow/internal/dao"
2026-03-04 19:17:16 +08:00
"ragflow/internal/service"
)
2026-06-01 11:22:08 +08:00
var IMG_BASE64_PREFIX = "data:image/png;base64,"
2026-06-03 20:55:53 +08:00
// documentServiceIface defines the DocumentService methods used by DocumentHandler.
type documentServiceIface interface {
CreateDocument ( req * service . CreateDocumentRequest ) ( * entity . Document , error )
GetDocumentByID ( id string ) ( * service . DocumentResponse , error )
UpdateDocument ( id string , req * service . UpdateDocumentRequest ) error
DeleteDocument ( id string ) error
DeleteDocuments ( ids [ ] string , deleteAll bool , datasetID , userID string ) ( int , error )
ParseDocuments ( datasetID , userID string , docIDs [ ] string ) ( [ ] * service . ParseDocumentResponse , error )
2026-06-04 14:16:13 +08:00
StopParseDocuments ( datasetID string , docIDs [ ] string ) ( map [ string ] interface { } , error )
2026-06-03 20:55:53 +08:00
ListDocuments ( page , pageSize int ) ( [ ] * service . DocumentResponse , int64 , error )
2026-07-02 15:57:07 +08:00
ListDocumentsByDatasetID ( kbID , keywords string , page , pageSize int ) ( [ ] * entity . DocumentListItem , int64 , error )
ListDocumentsByDatasetIDWithOptions ( opts dao . DocumentListOptions , page , pageSize int ) ( [ ] * entity . DocumentListItem , int64 , error )
ListDocumentIDsByDatasetIDWithOptions ( opts dao . DocumentListOptions ) ( [ ] string , error )
GetDocumentFiltersByDatasetID ( opts dao . DocumentListOptions ) ( map [ string ] interface { } , int64 , error )
GetMetadataByKBs ( kbIDs [ ] string ) ( map [ string ] interface { } , error )
2026-06-03 20:55:53 +08:00
GetDocumentsByAuthorID ( authorID , page , pageSize int ) ( [ ] * service . DocumentResponse , int64 , error )
2026-07-02 12:35:10 +08:00
GetThumbnails ( userID string , docIDs [ ] string ) ( map [ string ] string , error )
2026-06-03 20:55:53 +08:00
GetDocumentImage ( imageID string ) ( [ ] byte , error )
GetMetadataSummary ( kbID string , docIDs [ ] string ) ( map [ string ] interface { } , error )
SetDocumentMetadata ( docID string , meta map [ string ] interface { } ) error
DeleteDocumentMetadata ( docID string , keys [ ] string ) error
DeleteDocumentAllMetadata ( docID string ) error
GetDocumentMetadataByID ( docID string ) ( map [ string ] interface { } , error )
2026-06-30 16:28:48 +08:00
GetDocumentArtifact ( filename , userID string ) ( * service . ArtifactResponse , error )
2026-06-08 11:37:06 +08:00
GetDocumentPreview ( docID string ) ( * service . DocumentPreview , error )
2026-06-25 13:36:49 +08:00
UploadLocalDocuments ( kb * entity . Knowledgebase , tenantID string , files [ ] * multipart . FileHeader , parentPath string , parserConfigOverride map [ string ] interface { } ) ( [ ] map [ string ] interface { } , [ ] string )
UploadWebDocument ( kb * entity . Knowledgebase , tenantID , name , url string ) ( map [ string ] interface { } , common . ErrorCode , error )
UploadEmptyDocument ( kb * entity . Knowledgebase , tenantID , name string ) ( map [ string ] interface { } , common . ErrorCode , error )
2026-06-08 11:37:06 +08:00
DownloadDocument ( datasetID , docID string ) ( * service . DownloadDocumentResp , error )
2026-06-18 11:08:47 +08:00
UpdateDatasetDocument ( userID , datasetID , documentID string , req * service . UpdateDatasetDocumentRequest , present map [ string ] bool ) ( * service . UpdateDatasetDocumentResponse , common . ErrorCode , error )
2026-06-24 14:52:47 +08:00
BatchUpdateDocumentMetadatas ( datasetID string , selector * service . DocumentMetadataSelector , updates [ ] service . DocumentMetadataUpdate , deletes [ ] service . DocumentMetadataDelete ) ( * service . BatchUpdateDocumentMetadatasResponse , common . ErrorCode , error )
UploadDocumentInfos ( userID string , files [ ] * multipart . FileHeader ) ( [ ] map [ string ] interface { } , common . ErrorCode , error )
UploadDocumentInfoByURL ( userID , rawURL string ) ( map [ string ] interface { } , common . ErrorCode , error )
2026-06-12 14:56:44 +08:00
ListIngestionTasks ( userID string , datasetID * string , page , pageSize int ) ( [ ] * entity . IngestionTask , error )
IngestDocuments ( datasetID , userID string , docIDs [ ] string ) ( [ ] * service . ParseDocumentResponse , error )
StopIngestionTasks ( tasks [ ] string , userID string ) ( [ ] * entity . IngestionTask , error )
2026-06-24 19:43:18 +08:00
Ingest ( userID string , req * service . IngestDocumentRequest ) ( common . ErrorCode , error )
2026-06-12 14:56:44 +08:00
RemoveIngestionTasks ( tasks [ ] string , userID string ) ( [ ] map [ string ] string , error )
2026-06-23 19:19:08 +08:00
BatchUpdateDocumentStatus ( userID , datasetID , status string , DocumentIDs [ ] string ) ( map [ string ] interface { } , common . ErrorCode , error )
2026-06-03 20:55:53 +08:00
}
2026-03-04 19:17:16 +08:00
// DocumentHandler document handler
type DocumentHandler struct {
2026-06-03 20:55:53 +08:00
documentService documentServiceIface
2026-05-15 14:00:45 +08:00
datasetService * service . DatasetService
2026-03-04 19:17:16 +08:00
}
// NewDocumentHandler create document handler
2026-06-12 14:56:44 +08:00
func NewDocumentHandler ( documentService documentServiceIface , datasetService * service . DatasetService ) * DocumentHandler {
2026-03-04 19:17:16 +08:00
return & DocumentHandler {
documentService : documentService ,
2026-05-15 14:00:45 +08:00
datasetService : datasetService ,
2026-03-04 19:17:16 +08:00
}
}
// CreateDocument create document
// @Summary Create Document
// @Description Create new document
// @Tags documents
// @Accept json
// @Produce json
// @Param request body service.CreateDocumentRequest true "document info"
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/documents [post]
func ( h * DocumentHandler ) CreateDocument ( c * gin . Context ) {
2026-03-11 11:23:13 +08:00
_ , errorCode , errorMessage := GetUser ( c )
if errorCode != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( errorCode ) , errorMessage )
2026-03-11 11:23:13 +08:00
return
}
2026-03-04 19:17:16 +08:00
var req service . CreateDocumentRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
c . JSON ( http . StatusBadRequest , gin . H {
"error" : err . Error ( ) ,
} )
return
}
document , err := h . documentService . CreateDocument ( & req )
if err != nil {
c . JSON ( http . StatusInternalServerError , gin . H {
"error" : err . Error ( ) ,
} )
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , document , "created successfully" )
2026-03-04 19:17:16 +08:00
}
// GetDocumentByID get document by ID
// @Summary Get Document Info
// @Description Get document details by ID
// @Tags documents
// @Accept json
// @Produce json
// @Param id path int true "document ID"
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/documents/{id} [get]
func ( h * DocumentHandler ) GetDocumentByID ( c * gin . Context ) {
2026-03-11 11:23:13 +08:00
_ , errorCode , errorMessage := GetUser ( c )
if errorCode != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( errorCode ) , errorMessage )
2026-03-11 11:23:13 +08:00
return
}
2026-03-04 19:17:16 +08:00
id := c . Param ( "id" )
if id == "" {
c . JSON ( http . StatusBadRequest , gin . H {
"error" : "invalid document id" ,
} )
return
}
document , err := h . documentService . GetDocumentByID ( id )
if err != nil {
c . JSON ( http . StatusNotFound , gin . H {
"error" : "document not found" ,
} )
return
}
c . JSON ( http . StatusOK , gin . H {
"data" : document ,
} )
}
2026-06-01 11:22:08 +08:00
// GetThumbnail Get thumbnails for documents.
func ( h * DocumentHandler ) GetThumbnail ( c * gin . Context ) {
2026-07-02 12:35:10 +08:00
user , errorCode , errorMessage := GetUser ( c )
2026-06-01 11:22:08 +08:00
if errorCode != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( errorCode ) , errorMessage )
2026-06-01 11:22:08 +08:00
return
}
2026-07-02 12:35:10 +08:00
docIDs := parseThumbnailDocIDs ( c )
if len ( docIDs ) == 0 {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , ` Lack of "Document ID" ` )
2026-06-01 11:22:08 +08:00
return
}
2026-07-02 12:35:10 +08:00
result , err := h . documentService . GetThumbnails ( user . ID , docIDs )
2026-06-01 11:22:08 +08:00
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeServerError , nil , err . Error ( ) )
2026-06-01 11:22:08 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , result , "success" )
2026-06-01 11:22:08 +08:00
}
2026-07-02 12:35:10 +08:00
func parseThumbnailDocIDs ( c * gin . Context ) [ ] string {
rawValues := c . QueryArray ( "doc_ids" )
seen := make ( map [ string ] struct { } , len ( rawValues ) )
docIDs := make ( [ ] string , 0 , len ( rawValues ) )
for _ , raw := range rawValues {
id := strings . TrimSpace ( raw )
if id == "" {
continue
}
if _ , ok := seen [ id ] ; ok {
continue
}
seen [ id ] = struct { } { }
docIDs = append ( docIDs , id )
}
return docIDs
}
2026-06-01 11:22:08 +08:00
// GetDocumentImage returns a document image from object storage.
func ( h * DocumentHandler ) GetDocumentImage ( c * gin . Context ) {
imageID := c . Param ( "image_id" )
data , err := h . documentService . GetDocumentImage ( imageID )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeDataError , nil , "Image not found." )
2026-06-01 11:22:08 +08:00
return
}
2026-07-02 16:05:49 +08:00
contentType := documentImageContentType ( imageID , data )
2026-06-01 11:22:08 +08:00
c . Data ( http . StatusOK , contentType , data )
}
2026-07-02 16:05:49 +08:00
func documentImageContentType ( imageID string , data [ ] byte ) string {
if contentType := mime . TypeByExtension ( strings . ToLower ( filepath . Ext ( imageID ) ) ) ; strings . HasPrefix ( contentType , "image/" ) {
return contentType
}
switch {
case bytes . HasPrefix ( data , [ ] byte ( "\x89PNG\r\n\x1a\n" ) ) :
return "image/png"
case len ( data ) >= 3 && bytes . Equal ( data [ : 3 ] , [ ] byte { 0xff , 0xd8 , 0xff } ) :
return "image/jpeg"
case bytes . HasPrefix ( data , [ ] byte ( "GIF87a" ) ) , bytes . HasPrefix ( data , [ ] byte ( "GIF89a" ) ) :
return "image/gif"
case len ( data ) >= 12 && bytes . Equal ( data [ : 4 ] , [ ] byte ( "RIFF" ) ) && bytes . Equal ( data [ 8 : 12 ] , [ ] byte ( "WEBP" ) ) :
return "image/webp"
case bytes . HasPrefix ( data , [ ] byte ( "BM" ) ) :
return "image/bmp"
default :
return "application/octet-stream"
}
}
2026-06-08 11:37:06 +08:00
func ( h * DocumentHandler ) GetDocumentArtifact ( c * gin . Context ) {
2026-06-30 16:28:48 +08:00
user , code , msg := GetUser ( c )
if code != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , code , nil , msg )
2026-06-30 16:28:48 +08:00
return
}
2026-06-08 11:37:06 +08:00
filename := c . Param ( "filename" )
2026-06-30 16:28:48 +08:00
artifact , err := h . documentService . GetDocumentArtifact ( filename , user . ID )
2026-06-08 11:37:06 +08:00
if err != nil {
switch {
case errors . Is ( err , service . ErrArtifactInvalidFilename ) ,
errors . Is ( err , service . ErrArtifactInvalidFileType ) ,
errors . Is ( err , service . ErrArtifactNotFound ) :
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( common . CodeDataError ) , err . Error ( ) )
2026-06-08 11:37:06 +08:00
default :
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeExceptionError , nil , err . Error ( ) )
2026-06-08 11:37:06 +08:00
}
return
}
c . Header ( "Content-Type" , artifact . ContentType )
if artifact . ForceAttachment {
c . Header ( "X-Content-Type-Options" , "nosniff" )
c . Header ( "Content-Disposition" , "attachment" )
} else {
c . Header ( "Content-Disposition" , fmt . Sprintf ( ` inline; filename="%s" ` , artifact . SafeFilename ) )
}
c . Data ( http . StatusOK , artifact . ContentType , artifact . Data )
}
func ( h * DocumentHandler ) GetDocumentPreview ( c * gin . Context ) {
docID := c . Param ( "id" )
if docID == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeParamError , nil , "id is required" )
2026-06-08 11:37:06 +08:00
return
}
preview , err := h . documentService . GetDocumentPreview ( docID )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( common . CodeDataError ) , "Document not found!" )
2026-06-08 11:37:06 +08:00
return
}
ext := utility . GetFileExtension ( preview . FileName )
2026-07-06 10:58:40 +08:00
// Use the shared preview-headers helper so that safe types get
// Content-Disposition: inline with filename, while dangerous
// types (HTML, SVG, XML) fall back to forced attachment with
// nosniff. Mirrors Python document_api.py:2063 which calls
// apply_preview_file_response_headers() with the document name.
utility . SetPreviewFileResponseHeaders ( c . Writer . Header ( ) , preview . ContentType , ext , preview . FileName )
2026-06-08 11:37:06 +08:00
c . Data ( http . StatusOK , preview . ContentType , preview . Data )
}
2026-03-04 19:17:16 +08:00
// UpdateDocument update document
// @Summary Update Document
// @Description Update document info
// @Tags documents
// @Accept json
// @Produce json
// @Param id path int true "document ID"
// @Param request body service.UpdateDocumentRequest true "update info"
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/documents/{id} [put]
func ( h * DocumentHandler ) UpdateDocument ( c * gin . Context ) {
2026-07-06 10:13:46 +08:00
user , errorCode , errorMessage := GetUser ( c )
2026-03-11 11:23:13 +08:00
if errorCode != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( errorCode ) , errorMessage )
2026-03-11 11:23:13 +08:00
return
}
2026-03-04 19:17:16 +08:00
id := c . Param ( "id" )
if id == "" {
c . JSON ( http . StatusBadRequest , gin . H {
"error" : "invalid document id" ,
} )
return
}
2026-07-06 10:13:46 +08:00
doc , err := h . documentService . GetDocumentByID ( id )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , "document not found!" )
2026-07-06 10:13:46 +08:00
return
}
if ! h . datasetService . Accessible ( doc . KbID , user . ID ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeAuthenticationError , nil , "No authorization." )
2026-07-06 10:13:46 +08:00
return
}
2026-03-04 19:17:16 +08:00
var req service . UpdateDocumentRequest
2026-07-06 18:14:05 +08:00
if err = c . ShouldBindJSON ( & req ) ; err != nil {
2026-03-04 19:17:16 +08:00
c . JSON ( http . StatusBadRequest , gin . H {
"error" : err . Error ( ) ,
} )
return
}
2026-07-06 18:14:05 +08:00
if err = h . documentService . UpdateDocument ( id , & req ) ; err != nil {
2026-03-04 19:17:16 +08:00
c . JSON ( http . StatusInternalServerError , gin . H {
"error" : err . Error ( ) ,
} )
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithMessage ( c , "updated successfully" )
2026-03-04 19:17:16 +08:00
}
// DeleteDocument delete document
// @Summary Delete Document
// @Description Delete specified document
// @Tags documents
// @Accept json
// @Produce json
// @Param id path int true "document ID"
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/documents/{id} [delete]
func ( h * DocumentHandler ) DeleteDocument ( c * gin . Context ) {
2026-07-06 10:13:46 +08:00
user , errorCode , errorMessage := GetUser ( c )
2026-03-11 11:23:13 +08:00
if errorCode != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( errorCode ) , errorMessage )
2026-03-11 11:23:13 +08:00
return
}
2026-03-04 19:17:16 +08:00
id := c . Param ( "id" )
if id == "" {
c . JSON ( http . StatusBadRequest , gin . H {
"error" : "invalid document id" ,
} )
return
}
2026-07-06 10:13:46 +08:00
doc , err := h . documentService . GetDocumentByID ( id )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , "document not found!" )
2026-07-06 10:13:46 +08:00
return
}
if ! h . datasetService . Accessible ( doc . KbID , user . ID ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeAuthenticationError , nil , "No authorization." )
2026-07-06 10:13:46 +08:00
return
}
2026-07-06 18:14:05 +08:00
if err = h . documentService . DeleteDocument ( id ) ; err != nil {
2026-03-04 19:17:16 +08:00
c . JSON ( http . StatusInternalServerError , gin . H {
"error" : err . Error ( ) ,
} )
return
}
c . JSON ( http . StatusOK , gin . H {
"message" : "deleted successfully" ,
} )
}
2026-06-03 20:55:53 +08:00
// DeleteDocuments handles DELETE /api/v1/datasets/:dataset_id/documents
func ( h * DocumentHandler ) DeleteDocuments ( c * gin . Context ) {
_ , errorCode , errorMessage := GetUser ( c )
if errorCode != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( errorCode ) , errorMessage )
2026-06-03 20:55:53 +08:00
return
}
datasetID := c . Param ( "dataset_id" )
if datasetID == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "dataset_id is required" )
2026-06-03 20:55:53 +08:00
return
}
var req struct {
IDs * [ ] string ` json:"ids" `
DeleteAll bool ` json:"delete_all,omitempty" `
}
if c . Request . ContentLength > 0 {
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeDataError , nil , err . Error ( ) )
2026-06-03 20:55:53 +08:00
return
}
}
var ids [ ] string
if req . IDs != nil {
ids = * req . IDs
}
if len ( ids ) > 0 && req . DeleteAll {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "should not provide both ids and delete_all" )
2026-06-03 20:55:53 +08:00
return
}
if len ( ids ) == 0 && ! req . DeleteAll {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "should either provide doc ids or set delete_all(true)" )
2026-06-03 20:55:53 +08:00
return
}
userID := c . GetString ( "user_id" )
deleted , err := h . documentService . DeleteDocuments ( ids , req . DeleteAll , datasetID , userID )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeDataError , nil , err . Error ( ) )
2026-06-03 20:55:53 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , map [ string ] interface { } { "deleted" : deleted } , "success" )
2026-06-03 20:55:53 +08:00
}
2026-06-23 19:19:08 +08:00
// BatchUpdateDocumentStatus Batch update status of documents within a dataset.
func ( h * DocumentHandler ) BatchUpdateDocumentStatus ( c * gin . Context ) {
user , code , errorMessage := GetUser ( c )
if code != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , code , nil , errorMessage )
2026-06-23 19:19:08 +08:00
return
}
userID := strings . TrimSpace ( user . ID )
if userID == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "invalid user id" )
2026-06-23 19:19:08 +08:00
return
}
datasetID := strings . TrimSpace ( c . Param ( "dataset_id" ) )
if datasetID == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "dataset_id is required" )
2026-06-23 19:19:08 +08:00
return
}
var req struct {
DocumentIDs [ ] interface { } ` json:"doc_ids" `
Status interface { } ` json:"status" `
}
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeDataError , nil , err . Error ( ) )
2026-06-23 19:19:08 +08:00
return
}
if req . DocumentIDs == nil || len ( req . DocumentIDs ) == 0 {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , ` "doc_ids" must be a non-empty list. ` )
2026-06-23 19:19:08 +08:00
return
}
documentIDs := make ( [ ] string , 0 , len ( req . DocumentIDs ) )
for _ , rawDocID := range req . DocumentIDs {
docID , ok := rawDocID . ( string )
if ! ok || strings . TrimSpace ( docID ) == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil ,
` "doc_ids" must contain non-empty document IDs. ` )
2026-06-23 19:19:08 +08:00
return
}
documentIDs = append ( documentIDs , docID )
}
status := "-1"
if req . Status != nil {
status = fmt . Sprint ( req . Status )
}
if status != "0" && status != "1" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , fmt . Sprintf ( ` "Status" must be either 0 or 1:%s! ` , status ) )
2026-06-23 19:19:08 +08:00
return
}
result , code , err := h . documentService . BatchUpdateDocumentStatus ( userID , datasetID , status , documentIDs )
if err != nil {
message := err . Error ( )
if code == common . CodeServerError {
message = "Partial failure"
}
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , code , result , message )
2026-06-23 19:19:08 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , code , result , "success" )
2026-06-23 19:19:08 +08:00
}
2026-03-04 19:17:16 +08:00
// ListDocuments document list
func ( h * DocumentHandler ) ListDocuments ( c * gin . Context ) {
2026-03-11 11:23:13 +08:00
2026-05-15 14:00:45 +08:00
datasetID := c . Param ( "dataset_id" )
pageStr := c . Query ( "page" )
pageSizeStr := c . Query ( "page_size" )
page , _ := strconv . Atoi ( pageStr )
pageSize , _ := strconv . Atoi ( pageSizeStr )
userID := c . GetString ( "user_id" )
if ! h . datasetService . Accessible ( datasetID , userID ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeAuthenticationError , nil , "No authorization to access the dataset." )
2026-03-21 18:10:00 +08:00
return
}
2026-03-04 19:17:16 +08:00
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 10
}
2026-07-02 15:57:07 +08:00
opts , errMsg := parseDocumentListOptions ( c , datasetID )
if errMsg != "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeDataError , map [ string ] interface { } { "total" : 0 , "docs" : [ ] interface { } { } } , errMsg )
2026-07-02 15:57:07 +08:00
return
}
opts , errMsg = h . applyDocumentMetadataFilter ( c , opts )
if errMsg != "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeDataError , map [ string ] interface { } { "total" : 0 , "docs" : [ ] interface { } { } } , errMsg )
2026-07-02 15:57:07 +08:00
return
}
if c . Query ( "type" ) == "filter" {
filters , total , err := h . documentService . GetDocumentFiltersByDatasetID ( opts )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeExceptionError , map [ string ] interface { } { "total" : 0 , "filter" : map [ string ] interface { } { } } , "failed to get document filters" )
2026-07-02 15:57:07 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , gin . H { "total" : total , "filter" : filters } , "success" )
2026-07-02 15:57:07 +08:00
return
}
2026-03-21 18:10:00 +08:00
// Use kbID to filter documents
2026-07-02 15:57:07 +08:00
documents , total , err := h . documentService . ListDocumentsByDatasetIDWithOptions ( opts , page , pageSize )
2026-03-04 19:17:16 +08:00
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , 1 , map [ string ] interface { } { "total" : 0 , "docs" : [ ] interface { } { } } , "failed to get documents" )
2026-03-04 19:17:16 +08:00
return
}
2026-03-21 18:10:00 +08:00
docs := make ( [ ] map [ string ] interface { } , 0 , len ( documents ) )
for _ , doc := range documents {
metaFields , err := h . documentService . GetDocumentMetadataByID ( doc . ID )
if err != nil {
metaFields = make ( map [ string ] interface { } )
}
2026-05-18 20:00:11 +08:00
docs = append ( docs , mapDocumentListItem ( doc , metaFields ) )
2026-03-21 18:10:00 +08:00
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , gin . H { "total" : total , "docs" : docs } , "success" )
2026-03-04 19:17:16 +08:00
}
2026-07-02 15:57:07 +08:00
func parseDocumentListOptions ( c * gin . Context , datasetID string ) ( dao . DocumentListOptions , string ) {
opts := dao . DocumentListOptions {
KbID : datasetID ,
Keywords : c . Query ( "keywords" ) ,
OrderBy : c . DefaultQuery ( "orderby" , "create_time" ) ,
Desc : strings . ToLower ( strings . TrimSpace ( c . DefaultQuery ( "desc" , "true" ) ) ) != "false" ,
Suffixes : queryValues ( c , "suffix" ) ,
Types : queryValues ( c , "types" ) ,
}
opts . RunStatuses = normalizeRunStatusFilter ( queryValues ( c , "run" , "run_status" ) )
if len ( queryValues ( c , "run" , "run_status" ) ) > 0 && len ( opts . RunStatuses ) == 0 {
return opts , "Invalid filter run status conditions"
}
opts . Name = c . Query ( "name" )
docID := c . Query ( "id" )
docIDs := queryValues ( c , "ids" )
if docID != "" && len ( docIDs ) > 0 {
return opts , fmt . Sprintf ( "Should not provide both 'id':%s and 'ids'%v" , docID , docIDs )
}
if docID != "" {
opts . DocIDs = [ ] string { docID }
opts . DocIDFilterApplied = true
} else if len ( docIDs ) > 0 {
opts . DocIDs = docIDs
opts . DocIDFilterApplied = true
}
if v := c . Query ( "create_time_from" ) ; v != "" {
createTimeFrom , err := strconv . ParseInt ( v , 10 , 64 )
if err != nil {
return opts , "create_time_from must be an integer"
}
opts . CreateTimeFrom = createTimeFrom
}
if v := c . Query ( "create_time_to" ) ; v != "" {
createTimeTo , err := strconv . ParseInt ( v , 10 , 64 )
if err != nil {
return opts , "create_time_to must be an integer"
}
opts . CreateTimeTo = createTimeTo
}
return opts , ""
}
func ( h * DocumentHandler ) applyDocumentMetadataFilter ( c * gin . Context , opts dao . DocumentListOptions ) ( dao . DocumentListOptions , string ) {
metadata , err := parseMetadataQuery ( c . Request . URL . Query ( ) )
if err != nil {
return opts , err . Error ( )
}
returnEmptyMetadata := strings . ToLower ( strings . TrimSpace ( c . Query ( "return_empty_metadata" ) ) ) == "true"
if ! returnEmptyMetadata && len ( metadata ) == 0 {
return opts , ""
}
candidateIDs , err := h . documentService . ListDocumentIDsByDatasetIDWithOptions ( opts )
if err != nil {
return opts , "failed to get documents"
}
candidateSet := stringSet ( candidateIDs )
metadataByKey , err := h . documentService . GetMetadataByKBs ( [ ] string { opts . KbID } )
if err != nil {
return opts , err . Error ( )
}
docIDsWithMetadata := map [ string ] bool { }
matchedIDs := map [ string ] bool { }
firstMetadataKey := true
for key , values := range metadata {
valueMatches := map [ string ] bool { }
rawValues , _ := metadataByKey [ key ] . ( map [ string ] [ ] string )
for _ , value := range values {
for _ , docID := range rawValues [ value ] {
valueMatches [ docID ] = true
docIDsWithMetadata [ docID ] = true
}
}
if firstMetadataKey {
matchedIDs = valueMatches
firstMetadataKey = false
} else {
matchedIDs = intersectStringSets ( matchedIDs , valueMatches )
}
}
if returnEmptyMetadata {
for _ , rawValue := range metadataByKey {
values , _ := rawValue . ( map [ string ] [ ] string )
for _ , docIDs := range values {
for _ , docID := range docIDs {
docIDsWithMetadata [ docID ] = true
}
}
}
}
filteredIDs := make ( [ ] string , 0 )
if returnEmptyMetadata {
for _ , docID := range candidateIDs {
if ! docIDsWithMetadata [ docID ] {
filteredIDs = append ( filteredIDs , docID )
}
}
} else {
for docID := range matchedIDs {
if candidateSet [ docID ] {
filteredIDs = append ( filteredIDs , docID )
}
}
}
opts . DocIDs = filteredIDs
opts . DocIDFilterApplied = true
return opts , ""
}
func parseMetadataQuery ( values url . Values ) ( map [ string ] [ ] string , error ) {
metadata := map [ string ] [ ] string { }
if raw := strings . TrimSpace ( values . Get ( "metadata" ) ) ; raw != "" {
var parsed map [ string ] interface { }
if err := json . Unmarshal ( [ ] byte ( raw ) , & parsed ) ; err != nil {
return nil , fmt . Errorf ( "metadata must be valid JSON" )
}
for key , value := range parsed {
for _ , item := range interfaceToStringSlice ( value ) {
metadata [ key ] = append ( metadata [ key ] , item )
}
}
}
for key , vals := range values {
if ! strings . HasPrefix ( key , "metadata[" ) || ! strings . HasSuffix ( key , "]" ) {
continue
}
name := strings . TrimPrefix ( key , "metadata[" )
if end := strings . Index ( name , "]" ) ; end >= 0 {
name = name [ : end ]
}
if name == "" || name == "empty_metadata" {
continue
}
for _ , value := range vals {
for _ , item := range interfaceToStringSlice ( value ) {
metadata [ name ] = append ( metadata [ name ] , item )
}
}
}
return metadata , nil
}
func interfaceToStringSlice ( value interface { } ) [ ] string {
switch typed := value . ( type ) {
case [ ] interface { } :
out := make ( [ ] string , 0 , len ( typed ) )
for _ , item := range typed {
if item == nil {
continue
}
if s := strings . TrimSpace ( fmt . Sprintf ( "%v" , item ) ) ; s != "" {
out = append ( out , s )
}
}
return out
case [ ] string :
out := make ( [ ] string , 0 , len ( typed ) )
for _ , item := range typed {
if s := strings . TrimSpace ( item ) ; s != "" {
out = append ( out , s )
}
}
return out
case string :
if strings . TrimSpace ( typed ) == "" {
return nil
}
return [ ] string { strings . TrimSpace ( typed ) }
default :
if value == nil {
return nil
}
return [ ] string { fmt . Sprintf ( "%v" , value ) }
}
}
func stringSet ( values [ ] string ) map [ string ] bool {
out := make ( map [ string ] bool , len ( values ) )
for _ , value := range values {
out [ value ] = true
}
return out
}
func intersectStringSets ( left , right map [ string ] bool ) map [ string ] bool {
out := make ( map [ string ] bool )
for value := range left {
if right [ value ] {
out [ value ] = true
}
}
return out
}
func queryValues ( c * gin . Context , names ... string ) [ ] string {
values := make ( [ ] string , 0 )
for _ , name := range names {
values = append ( values , c . QueryArray ( name ) ... )
values = append ( values , c . QueryArray ( name + "[]" ) ... )
}
out := make ( [ ] string , 0 , len ( values ) )
for _ , value := range values {
if trimmed := strings . TrimSpace ( value ) ; trimmed != "" {
out = append ( out , trimmed )
}
}
return out
}
func normalizeRunStatusFilter ( statuses [ ] string ) [ ] string {
if len ( statuses ) == 0 {
return nil
}
statusTextToNumeric := map [ string ] string {
"UNSTART" : string ( entity . TaskStatusUnstart ) ,
"RUNNING" : string ( entity . TaskStatusRunning ) ,
"CANCEL" : string ( entity . TaskStatusCancel ) ,
"DONE" : string ( entity . TaskStatusDone ) ,
"FAIL" : string ( entity . TaskStatusFail ) ,
}
validStatuses := map [ string ] bool {
string ( entity . TaskStatusUnstart ) : true ,
string ( entity . TaskStatusRunning ) : true ,
string ( entity . TaskStatusCancel ) : true ,
string ( entity . TaskStatusDone ) : true ,
string ( entity . TaskStatusFail ) : true ,
}
out := make ( [ ] string , 0 , len ( statuses ) )
for _ , status := range statuses {
normalized := statusTextToNumeric [ strings . ToUpper ( status ) ]
if normalized == "" {
normalized = status
}
if ! validStatuses [ normalized ] {
return nil
}
out = append ( out , normalized )
}
return out
}
2026-06-25 13:36:49 +08:00
func ( h * DocumentHandler ) UploadDocuments ( c * gin . Context ) {
user , errorCode , errorMessage := GetUser ( c )
if errorCode != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( errorCode ) , errorMessage )
2026-06-25 13:36:49 +08:00
return
}
tenantID := user . ID
datasetID := c . Param ( "dataset_id" )
uploadType := strings . ToLower ( c . DefaultQuery ( "type" , "local" ) )
kb , err := h . datasetService . GetKnowledgebaseByID ( datasetID )
if err != nil || kb == nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeDataError , nil , fmt . Sprintf ( "Can't find the dataset with ID %s!" , datasetID ) )
2026-06-25 13:36:49 +08:00
return
}
if ! h . datasetService . CheckKBTeamPermission ( kb , tenantID ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeAuthenticationError , nil , "No authorization." )
2026-06-25 13:36:49 +08:00
return
}
switch uploadType {
case "web" :
h . uploadWebDocument ( c , kb , tenantID )
case "empty" :
h . uploadEmptyDocument ( c , kb , tenantID )
case "local" :
h . uploadLocalDocuments ( c , kb , tenantID )
default :
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , ` "type" must be one of "local", "web", or "empty". ` )
2026-06-25 13:36:49 +08:00
}
}
func ( h * DocumentHandler ) uploadLocalDocuments ( c * gin . Context , kb * entity . Knowledgebase , tenantID string ) {
form , err := c . MultipartForm ( )
if err != nil || form == nil || len ( form . File [ "file" ] ) == 0 {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "No file part!" )
2026-06-25 13:36:49 +08:00
return
}
files := form . File [ "file" ]
for _ , fh := range files {
if fh == nil || fh . Filename == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil ,
"No file selected!" )
2026-06-25 13:36:49 +08:00
return
}
if len ( [ ] byte ( fh . Filename ) ) > 255 {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil ,
"File name must be 255 bytes or less." )
2026-06-25 13:36:49 +08:00
return
}
}
// Optional parser_config override — only the allow-listed table column keys.
// Python ignores malformed or non-object input here instead of failing the
// whole upload request.
var override map [ string ] interface { }
if raw := strings . TrimSpace ( c . PostForm ( "parser_config" ) ) ; raw != "" {
var parsed map [ string ] interface { }
2026-07-06 18:14:05 +08:00
if err = json . Unmarshal ( [ ] byte ( raw ) , & parsed ) ; err == nil && parsed != nil {
2026-06-25 13:36:49 +08:00
override = map [ string ] interface { } { }
for _ , k := range [ ] string { "table_column_mode" , "table_column_roles" } {
if v , ok := parsed [ k ] ; ok {
override [ k ] = v
}
}
if len ( override ) == 0 {
override = nil
}
}
}
data , errMsgs := h . documentService . UploadLocalDocuments ( kb , tenantID , files , c . PostForm ( "parent_path" ) , override )
if len ( data ) == 0 && len ( errMsgs ) > 0 {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeServerError , nil , strings . Join ( errMsgs , "\n" ) )
2026-06-25 13:36:49 +08:00
return
}
if len ( data ) == 0 {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeDataError , nil , "There seems to be an issue with your file format. please verify it is correct and not corrupted." )
2026-06-25 13:36:49 +08:00
return
}
if strings . ToLower ( c . DefaultQuery ( "return_raw_files" , "false" ) ) == "true" {
if len ( errMsgs ) > 0 {
2026-07-06 19:15:29 +08:00
common . ResponseWithCodeData ( c , common . CodeServerError , data , strings . Join ( errMsgs , "\n" ) )
2026-06-25 13:36:49 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessNoMessage ( c , data )
2026-06-25 13:36:49 +08:00
return
}
mapped := make ( [ ] map [ string ] interface { } , len ( data ) )
for i , d := range data {
mapped [ i ] = mapDocKeysWithRunStatus ( d )
}
if len ( errMsgs ) > 0 {
2026-07-06 19:15:29 +08:00
common . ResponseWithCodeData ( c , common . CodeServerError , mapped , strings . Join ( errMsgs , "\n" ) )
2026-06-25 13:36:49 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessNoMessage ( c , mapped )
2026-06-25 13:36:49 +08:00
}
func ( h * DocumentHandler ) uploadEmptyDocument ( c * gin . Context , kb * entity . Knowledgebase , tenantID string ) {
var req struct {
Name string ` json:"name" `
}
// An empty body is valid (falls through to the name-required check below);
// a non-empty but malformed body should report the syntax error, not a
// misleading "File name can't be empty."
if err := c . ShouldBindJSON ( & req ) ; err != nil && ! errors . Is ( err , io . EOF ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "Invalid JSON body: " + err . Error ( ) )
2026-06-25 13:36:49 +08:00
return
}
name := strings . TrimSpace ( req . Name )
if name == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "File name can't be empty." )
2026-06-25 13:36:49 +08:00
return
}
if len ( [ ] byte ( name ) ) > 255 {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "File name must be 255 bytes or less." )
2026-06-25 13:36:49 +08:00
return
}
data , code , err := h . documentService . UploadEmptyDocument ( kb , tenantID , name )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( code ) , err . Error ( ) )
2026-06-25 13:36:49 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessNoMessage ( c , mapDocKeysWithRunStatus ( data ) )
2026-06-25 13:36:49 +08:00
}
func ( h * DocumentHandler ) uploadWebDocument ( c * gin . Context , kb * entity . Knowledgebase , tenantID string ) {
name := strings . TrimSpace ( c . PostForm ( "name" ) )
rawURL := c . PostForm ( "url" )
if name == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , ` Lack of "name" ` )
2026-06-25 13:36:49 +08:00
return
}
if rawURL == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , ` Lack of "url" ` )
2026-06-25 13:36:49 +08:00
return
}
if len ( [ ] byte ( name ) ) > 255 {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "File name must be 255 bytes or less." )
2026-06-25 13:36:49 +08:00
return
}
if ! isValidHTTPURL ( rawURL ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "The URL format is invalid" )
2026-06-25 13:36:49 +08:00
return
}
data , code , err := h . documentService . UploadWebDocument ( kb , tenantID , name , rawURL )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( code ) , err . Error ( ) )
2026-06-25 13:36:49 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessNoMessage ( c , mapDocKeysWithRunStatus ( data ) )
2026-06-25 13:36:49 +08:00
}
// mapDocKeysWithRunStatus renames a freshly-created document's raw keys to the
// public response shape (chunk_num→chunk_count, token_num→token_count,
// kb_id→dataset_id, parser_id→chunk_method) and reports run as a label.
// Mirrors Python map_doc_keys_with_run_status / map_doc_keys.
func mapDocKeysWithRunStatus ( raw map [ string ] interface { } ) map [ string ] interface { } {
out := map [ string ] interface { } {
"chunk_count" : raw [ "chunk_num" ] ,
"token_count" : raw [ "token_num" ] ,
"dataset_id" : raw [ "kb_id" ] ,
"chunk_method" : raw [ "parser_id" ] ,
"run" : "UNSTART" ,
}
for _ , k := range [ ] string { "id" , "name" , "type" , "size" , "suffix" , "source_type" , "created_by" , "parser_config" , "location" , "pipeline_id" , "content_hash" } {
if v , ok := raw [ k ] ; ok {
out [ k ] = v
}
}
return out
}
// isValidHTTPURL mirrors Python is_valid_url: requires an http/https scheme and a host.
func isValidHTTPURL ( raw string ) bool {
u , err := url . Parse ( strings . TrimSpace ( raw ) )
if err != nil {
return false
}
return ( u . Scheme == "http" || u . Scheme == "https" ) && u . Host != ""
}
2026-06-08 11:37:06 +08:00
func ( h * DocumentHandler ) DownloadDocument ( c * gin . Context ) {
datasetID := c . Param ( "dataset_id" )
docID := c . Param ( "document_id" )
if docID == "" {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( common . CodeDataError ) , "Specify document_id please." )
2026-06-08 11:37:06 +08:00
return
}
if datasetID == "" {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( common . CodeDataError ) , fmt . Sprintf ( "The dataset not own the document %s." , docID ) )
2026-06-08 11:37:06 +08:00
return
}
res , err := h . documentService . DownloadDocument ( datasetID , docID )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( common . CodeDataError ) , err . Error ( ) )
2026-06-08 11:37:06 +08:00
return
}
c . Header ( "Content-Type" , res . ContentType )
c . Header ( "Content-Disposition" , fmt . Sprintf ( ` attachment; filename="%s" ` , res . FileName ) )
c . Data ( http . StatusOK , res . ContentType , res . Data )
}
2026-05-18 20:00:11 +08:00
func mapDocumentListItem ( doc * entity . DocumentListItem , metaFields map [ string ] interface { } ) map [ string ] interface { } {
item := map [ string ] interface { } {
"id" : doc . ID ,
"dataset_id" : doc . KbID ,
"name" : stringValue ( doc . Name ) ,
"thumbnail" : stringValue ( doc . Thumbnail ) ,
"size" : doc . Size ,
"type" : doc . Type ,
"created_by" : doc . CreatedBy ,
"location" : stringValue ( doc . Location ) ,
"token_count" : doc . TokenNum ,
"chunk_count" : doc . ChunkNum ,
"progress" : doc . Progress ,
"progress_msg" : stringValue ( doc . ProgressMsg ) ,
"process_begin_at" : formatTimePtr ( doc . ProcessBeginAt ) ,
"process_duration" : doc . ProcessDuration ,
"suffix" : doc . Suffix ,
"run" : mapRunStatus ( doc . Run ) ,
"status" : stringValue ( doc . Status ) ,
"chunk_method" : doc . ParserID ,
"parser_id" : doc . ParserID ,
"pipeline_id" : stringValue ( doc . PipelineID ) ,
"pipeline_name" : stringValue ( doc . PipelineName ) ,
"nickname" : stringValue ( doc . Nickname ) ,
"parser_config" : decodeJSONMap ( string ( doc . ParserConfig ) ) ,
"meta_fields" : metaFields ,
"create_time" : int64 ( 0 ) ,
"create_date" : "" ,
"update_time" : int64 ( 0 ) ,
"update_date" : "" ,
}
if doc . CreateTime != nil {
item [ "create_time" ] = * doc . CreateTime
}
if doc . CreateDate != nil {
item [ "create_date" ] = doc . CreateDate . Format ( "2006-01-02 15:04:05" )
}
if doc . UpdateTime != nil {
item [ "update_time" ] = * doc . UpdateTime
}
if doc . UpdateDate != nil {
item [ "update_date" ] = doc . UpdateDate . Format ( "2006-01-02 15:04:05" )
}
return item
}
func decodeJSONMap ( raw string ) map [ string ] interface { } {
if strings . TrimSpace ( raw ) == "" {
return map [ string ] interface { } { }
}
var data map [ string ] interface { }
if err := json . Unmarshal ( [ ] byte ( raw ) , & data ) ; err != nil {
return map [ string ] interface { } { }
}
return data
}
func mapRunStatus ( run * string ) string {
if run == nil {
return "UNSTART"
}
switch strings . TrimSpace ( * run ) {
case "0" :
return "UNSTART"
case "1" :
return "RUNNING"
case "2" :
return "CANCEL"
case "3" :
return "DONE"
case "4" :
return "FAIL"
default :
return strings . TrimSpace ( * run )
}
}
func formatTimePtr ( value * time . Time ) string {
if value == nil {
return ""
}
return value . Format ( "2006-01-02 15:04:05" )
}
func stringValue ( value * string ) string {
if value == nil {
return ""
}
return * value
}
2026-03-21 18:10:00 +08:00
// MetadataSummary handles the metadata summary request
func ( h * DocumentHandler ) MetadataSummary ( c * gin . Context ) {
_ , errorCode , errorMessage := GetUser ( c )
if errorCode != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( errorCode ) , errorMessage )
2026-03-21 18:10:00 +08:00
return
}
var requestBody struct {
KBID string ` json:"kb_id" binding:"required" `
DocIDs [ ] string ` json:"doc_ids" `
}
if err := c . ShouldBindJSON ( & requestBody ) ; err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , "kb_id is required" )
2026-03-21 18:10:00 +08:00
return
}
kbID := requestBody . KBID
if kbID == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , "kb_id is required" )
2026-03-21 18:10:00 +08:00
return
}
summary , err := h . documentService . GetMetadataSummary ( kbID , requestBody . DocIDs )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusInternalServerError , 1 , nil , "Failed to get metadata summary: " + err . Error ( ) )
2026-03-21 18:10:00 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , gin . H { "summary" : summary } , "success" )
2026-03-21 18:10:00 +08:00
}
2026-04-07 09:44:51 +08:00
// 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 ) {
2026-07-06 10:13:46 +08:00
user , errorCode , errorMessage := GetUser ( c )
2026-04-07 09:44:51 +08:00
if errorCode != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( errorCode ) , errorMessage )
2026-04-07 09:44:51 +08:00
return
}
var req SetMetaRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , err . Error ( ) )
2026-04-07 09:44:51 +08:00
return
}
if req . DocID == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , "doc_id is required" )
2026-04-07 09:44:51 +08:00
return
}
// Parse meta JSON string
var meta map [ string ] interface { }
if err := json . Unmarshal ( [ ] byte ( req . Meta ) , & meta ) ; err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , "Json syntax error: " + err . Error ( ) )
2026-04-07 09:44:51 +08:00
return
}
if meta == nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , "meta is required" )
2026-04-07 09:44:51 +08:00
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 {
2026-07-06 18:14:05 +08:00
if _ , ok = item . ( float64 ) ; ! ok {
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , fmt . Sprintf ( "Unsupported type in list for key %s: %T" , k , item ) )
2026-04-07 09:44:51 +08:00
return
}
}
}
default :
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , fmt . Sprintf ( "Unsupported type for key %s: %T" , k , v ) )
2026-04-07 09:44:51 +08:00
return
}
}
2026-07-06 10:13:46 +08:00
// Authorization: user must be able to access the document's dataset.
doc , err := h . documentService . GetDocumentByID ( req . DocID )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , "document not found" )
2026-07-06 10:13:46 +08:00
return
}
if ! h . datasetService . Accessible ( doc . KbID , user . ID ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeAuthenticationError , nil , "No authorization." )
2026-07-06 10:13:46 +08:00
return
}
err = h . documentService . SetDocumentMetadata ( req . DocID , meta )
2026-04-07 09:44:51 +08:00
if err != nil {
2026-04-09 09:52:31 +08:00
errMsg := err . Error ( )
if strings . Contains ( errMsg , "no such document" ) || strings . Contains ( errMsg , "document not found" ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , errMsg )
2026-04-09 09:52:31 +08:00
} else {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusInternalServerError , 1 , nil , "Failed to set metadata: " + errMsg )
2026-04-09 09:52:31 +08:00
}
2026-04-07 09:44:51 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , true , "success" )
2026-05-15 14:00:45 +08:00
}
2026-06-24 19:43:18 +08:00
func ( h * DocumentHandler ) Ingest ( c * gin . Context ) {
user , errorCode , errorMessage := GetUser ( c )
if errorCode != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( errorCode ) , errorMessage )
2026-06-24 19:43:18 +08:00
return
}
userID := strings . TrimSpace ( user . ID )
if userID == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeAuthenticationError , nil , "No Authentication" )
2026-06-24 19:43:18 +08:00
return
}
var req service . IngestDocumentRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( common . CodeBadRequest ) , err . Error ( ) )
2026-06-24 19:43:18 +08:00
return
}
if code , err := h . documentService . Ingest ( userID , & req ) ; err != nil {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( code ) , err . Error ( ) )
2026-06-24 19:43:18 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , true , "success" )
2026-06-24 19:43:18 +08:00
}
2026-05-25 19:15:07 +08:00
// 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 /v1/document/delete_meta [post]
func ( h * DocumentHandler ) DeleteMeta ( c * gin . Context ) {
user , errorCode , errorMessage := GetUser ( c )
if errorCode != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( errorCode ) , errorMessage )
2026-05-25 19:15:07 +08:00
return
}
var req DeleteMetaRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , err . Error ( ) )
2026-05-25 19:15:07 +08:00
return
}
if req . DocID == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , "doc_id is required" )
2026-05-25 19:15:07 +08:00
return
}
// Authorization: user must be able to access the document's dataset.
doc , err := h . documentService . GetDocumentByID ( req . DocID )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , "document not found" )
2026-05-25 19:15:07 +08:00
return
}
if ! h . datasetService . Accessible ( doc . KbID , user . ID ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeAuthenticationError , nil , "No authorization." )
2026-05-25 19:15:07 +08:00
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
2026-07-06 18:14:05 +08:00
if err = json . Unmarshal ( [ ] byte ( req . Keys ) , & keys ) ; err != nil {
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , "Json syntax error: " + err . Error ( ) )
2026-05-25 19:15:07 +08:00
return
}
if keys == nil || len ( keys ) == 0 {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , "keys list is required" )
2026-05-25 19:15:07 +08:00
return
}
2026-07-06 18:14:05 +08:00
err = h . documentService . DeleteDocumentMetadata ( req . DocID , keys )
2026-05-25 19:15:07 +08:00
if err != nil {
errMsg := err . Error ( )
if strings . Contains ( errMsg , "no such document" ) || strings . Contains ( errMsg , "document not found" ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , errMsg )
2026-05-25 19:15:07 +08:00
} else {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusInternalServerError , 1 , nil , "Failed to delete metadata: " + errMsg )
2026-05-25 19:15:07 +08:00
}
return
}
} else {
// Delete entire document metadata
2026-07-06 18:14:05 +08:00
err = h . documentService . DeleteDocumentAllMetadata ( req . DocID )
2026-05-25 19:15:07 +08:00
if err != nil {
errMsg := err . Error ( )
if strings . Contains ( errMsg , "no such document" ) || strings . Contains ( errMsg , "document not found" ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusBadRequest , 1 , nil , errMsg )
2026-05-25 19:15:07 +08:00
} else {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusInternalServerError , 1 , nil , "Failed to delete metadata: " + errMsg )
2026-05-25 19:15:07 +08:00
}
return
}
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , true , "success" )
2026-05-25 19:15:07 +08:00
}
2026-06-12 14:56:44 +08:00
type ListIngestionsRequest struct {
DatasetID * string ` json:"dataset_id" `
}
func ( h * DocumentHandler ) ListIngestionTasks ( c * gin . Context ) {
var req ListIngestionsRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( common . CodeBadRequest ) , err . Error ( ) )
2026-06-12 14:56:44 +08:00
return
}
userID := c . GetString ( "user_id" )
var parseResult [ ] * entity . IngestionTask
var err error
if req . DatasetID != nil {
if ! h . datasetService . Accessible ( * req . DatasetID , userID ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeAuthenticationError , nil , "No authorization to access the dataset." )
2026-06-12 14:56:44 +08:00
return
}
}
parseResult , err = h . documentService . ListIngestionTasks ( userID , req . DatasetID , 0 , 0 )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeExceptionError , nil , err . Error ( ) )
2026-06-12 14:56:44 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , parseResult , "success" )
2026-06-12 14:56:44 +08:00
}
type StartParseDocumentsRequest struct {
DatasetID string ` json:"dataset_id" binding:"required" `
Documents [ ] string ` json:"documents" binding:"required" `
}
func ( h * DocumentHandler ) StartIngestionTask ( c * gin . Context ) {
var req StartParseDocumentsRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( common . CodeBadRequest ) , err . Error ( ) )
2026-06-12 14:56:44 +08:00
return
}
userID := c . GetString ( "user_id" )
if ! h . datasetService . Accessible ( req . DatasetID , userID ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeAuthenticationError , nil , "No authorization to access the dataset." )
2026-06-12 14:56:44 +08:00
return
}
parseResult , err := h . documentService . IngestDocuments ( req . DatasetID , userID , req . Documents )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeExceptionError , nil , err . Error ( ) )
2026-06-12 14:56:44 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , parseResult , "success" )
2026-06-12 14:56:44 +08:00
}
type StopIngestionsRequest struct {
Tasks [ ] string ` json:"tasks" binding:"required" `
}
func ( h * DocumentHandler ) StopIngestionTasks ( c * gin . Context ) {
var req StopIngestionsRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( common . CodeBadRequest ) , err . Error ( ) )
2026-06-12 14:56:44 +08:00
return
}
userID := c . GetString ( "user_id" )
parseResult , err := h . documentService . StopIngestionTasks ( req . Tasks , userID )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeExceptionError , nil , err . Error ( ) )
2026-06-12 14:56:44 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , parseResult , "success" )
2026-06-12 14:56:44 +08:00
}
type RemoveIngestionsRequest struct {
Tasks [ ] string ` json:"tasks" binding:"required" `
}
func ( h * DocumentHandler ) RemoveIngestionTasks ( c * gin . Context ) {
var req RemoveIngestionsRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( common . CodeBadRequest ) , err . Error ( ) )
2026-06-12 14:56:44 +08:00
return
}
if req . Tasks == nil || len ( req . Tasks ) == 0 {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , 1 , "task_ids is required" )
2026-06-12 14:56:44 +08:00
return
}
userID := c . GetString ( "user_id" )
deletedTasks , err := h . documentService . RemoveIngestionTasks ( req . Tasks , userID )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeExceptionError , nil , err . Error ( ) )
2026-06-12 14:56:44 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , deletedTasks , "success" )
2026-06-12 14:56:44 +08:00
}
2026-05-15 14:00:45 +08:00
type ParseDocumentRequest struct {
Documents [ ] string ` json:"documents" binding:"required" `
}
func ( h * DocumentHandler ) ParseDocuments ( c * gin . Context ) {
2026-05-25 14:00:08 +08:00
datasetID := c . Param ( "dataset_id" )
2026-05-15 14:00:45 +08:00
var req ParseDocumentRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( common . CodeBadRequest ) , err . Error ( ) )
2026-05-15 14:00:45 +08:00
return
}
userID := c . GetString ( "user_id" )
2026-05-25 14:00:08 +08:00
if ! h . datasetService . Accessible ( datasetID , userID ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeAuthenticationError , nil , "No authorization to access the dataset." )
2026-05-15 14:00:45 +08:00
return
}
2026-05-25 14:00:08 +08:00
parseResult , err := h . documentService . ParseDocuments ( datasetID , userID , req . Documents )
2026-05-15 14:00:45 +08:00
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeExceptionError , nil , err . Error ( ) )
2026-05-15 14:00:45 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , parseResult , "success" )
2026-05-15 14:00:45 +08:00
}
2026-06-04 14:16:13 +08:00
type StopParseDocumentRequest struct {
DocumentIDs [ ] string ` json:"document_ids" binding:"required" `
}
func ( h * DocumentHandler ) StopParseDocuments ( c * gin . Context ) {
datasetID := c . Param ( "dataset_id" )
var req StopParseDocumentRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( common . CodeBadRequest ) , err . Error ( ) )
2026-06-04 14:16:13 +08:00
return
}
if len ( req . DocumentIDs ) == 0 {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( common . CodeBadRequest ) , "`document_ids` is required" )
2026-06-04 14:16:13 +08:00
return
}
userID := c . GetString ( "user_id" )
if ! h . datasetService . Accessible ( datasetID , userID ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeAuthenticationError , nil , "You don't own the dataset." )
2026-06-04 14:16:13 +08:00
return
}
result , err := h . documentService . StopParseDocuments ( datasetID , req . DocumentIDs )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeExceptionError , nil , err . Error ( ) )
2026-06-04 14:16:13 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , result , "success" )
2026-06-04 14:16:13 +08:00
}
2026-06-09 19:27:47 +08:00
func ( h * DocumentHandler ) MetadataSummaryByDataset ( c * gin . Context ) {
user , errorCode , errorMessage := GetUser ( c )
if errorCode != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( errorCode ) , errorMessage )
2026-06-09 19:27:47 +08:00
return
}
datasetID := c . Param ( "dataset_id" )
if datasetID == "" {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( common . CodeServerError ) , "dataset_id is required" )
2026-06-09 19:27:47 +08:00
return
}
if ! h . datasetService . Accessible ( datasetID , user . ID ) {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( common . CodeServerError ) , "You don't own the dataset " + datasetID )
2026-06-09 19:27:47 +08:00
return
}
var docIDS [ ] string
if docIDsParam := c . Query ( "doc_ids" ) ; docIDsParam != "" {
docIDS = strings . Split ( docIDsParam , "," )
}
summary , err := h . documentService . GetMetadataSummary ( datasetID , docIDS )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithHttpCodeData ( c , http . StatusInternalServerError , common . CodeServerError , nil , "Failed to get metadata summary" + err . Error ( ) )
2026-06-09 19:27:47 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , gin . H { "summary" : summary } , "success" )
2026-06-09 19:27:47 +08:00
}
2026-06-18 11:08:47 +08:00
func ( h * DocumentHandler ) UpdateDatasetDocument ( c * gin . Context ) {
user , errorCode , errorMessage := GetUser ( c )
if errorCode != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( errorCode ) , errorMessage )
2026-06-18 11:08:47 +08:00
return
}
datasetID := strings . TrimSpace ( c . Param ( "dataset_id" ) )
if datasetID == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "dataset_id is required" )
2026-06-18 11:08:47 +08:00
return
}
documentID := strings . TrimSpace ( c . Param ( "document_id" ) )
if documentID == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "document_id is required" )
2026-06-18 11:08:47 +08:00
return
}
body , err := c . GetRawData ( )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeDataError , nil , err . Error ( ) )
2026-06-18 11:08:47 +08:00
return
}
var raw map [ string ] json . RawMessage
if err := json . Unmarshal ( body , & raw ) ; err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeDataError , nil , err . Error ( ) )
2026-06-18 11:08:47 +08:00
return
}
present := make ( map [ string ] bool , len ( raw ) )
for key := range raw {
present [ key ] = true
}
var req service . UpdateDatasetDocumentRequest
2026-07-06 18:14:05 +08:00
if err = json . Unmarshal ( body , & req ) ; err != nil {
common . ResponseWithCodeData ( c , common . CodeDataError , nil , err . Error ( ) )
2026-06-18 11:08:47 +08:00
return
}
data , code , err := h . documentService . UpdateDatasetDocument ( user . ID , datasetID , documentID , & req , present )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( code ) , err . Error ( ) )
2026-06-18 11:08:47 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessNoMessage ( c , data )
2026-06-18 11:08:47 +08:00
}
2026-06-24 14:52:47 +08:00
func ( h * DocumentHandler ) UploadInfo ( c * gin . Context ) {
user , errorCode , errorMessage := GetUser ( c )
if errorCode != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( errorCode ) , errorMessage )
2026-06-24 14:52:47 +08:00
return
}
form , err := c . MultipartForm ( )
if err != nil && ! strings . Contains ( err . Error ( ) , "request Content-Type isn't multipart/form-data" ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "Failed to parse multipart form: " + err . Error ( ) )
2026-06-24 14:52:47 +08:00
return
}
var fileHeaders [ ] * multipart . FileHeader
if form != nil && form . File != nil {
fileHeaders = form . File [ "file" ]
}
rawURL := strings . TrimSpace ( c . Query ( "url" ) )
if len ( fileHeaders ) > 0 && rawURL != "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "Provide either multipart file(s) or ?url=..., not both." )
2026-06-24 14:52:47 +08:00
return
}
if len ( fileHeaders ) == 0 && rawURL == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "Missing input: provide multipart file(s) or url" )
2026-06-24 14:52:47 +08:00
return
}
if rawURL != "" {
data , code , err := h . documentService . UploadDocumentInfoByURL ( user . ID , rawURL )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( code ) , err . Error ( ) )
2026-06-24 14:52:47 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , data , "success" )
2026-06-24 14:52:47 +08:00
return
}
data , code , err := h . documentService . UploadDocumentInfos ( user . ID , fileHeaders )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( code ) , err . Error ( ) )
2026-06-24 14:52:47 +08:00
return
}
var payload interface { }
if len ( data ) == 1 {
payload = data [ 0 ]
} else {
payload = data
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , payload , "success" )
2026-06-24 14:52:47 +08:00
}
type documentMetadataBatchRequest struct {
Selector * service . DocumentMetadataSelector ` json:"selector" `
Updates [ ] service . DocumentMetadataUpdate ` json:"updates" `
Deletes [ ] service . DocumentMetadataDelete ` json:"deletes" `
}
func ( h * DocumentHandler ) MetadataBatchUpdate ( c * gin . Context ) {
h . handleBatchUpdateDocumentMetadatas ( c )
}
func ( h * DocumentHandler ) UpdateDocumentMetadatas ( c * gin . Context ) {
h . handleBatchUpdateDocumentMetadatas ( c )
}
func ( h * DocumentHandler ) handleBatchUpdateDocumentMetadatas ( c * gin . Context ) {
user , errorCode , errorMessage := GetUser ( c )
if errorCode != common . CodeSuccess {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( errorCode ) , errorMessage )
2026-06-24 14:52:47 +08:00
return
}
datasetID := strings . TrimSpace ( c . Param ( "dataset_id" ) )
if datasetID == "" {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeArgumentError , nil , "dataset_id is required" )
2026-06-24 14:52:47 +08:00
return
}
if ! h . datasetService . Accessible ( datasetID , user . ID ) {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeDataError , nil , "You don't own the dataset " + datasetID + "." )
2026-06-24 14:52:47 +08:00
return
}
var req documentMetadataBatchRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
2026-07-06 18:14:05 +08:00
common . ResponseWithCodeData ( c , common . CodeDataError , nil , err . Error ( ) )
2026-06-24 14:52:47 +08:00
return
}
if req . Selector == nil {
req . Selector = & service . DocumentMetadataSelector { }
}
if req . Updates == nil {
req . Updates = [ ] service . DocumentMetadataUpdate { }
}
if req . Deletes == nil {
req . Deletes = [ ] service . DocumentMetadataDelete { }
}
resp , code , err := h . documentService . BatchUpdateDocumentMetadatas ( datasetID , req . Selector , req . Updates , req . Deletes )
if err != nil {
2026-07-06 18:14:05 +08:00
common . ErrorWithCode ( c , int ( code ) , err . Error ( ) )
2026-06-24 14:52:47 +08:00
return
}
2026-07-06 18:14:05 +08:00
common . SuccessWithData ( c , resp , "success" )
2026-06-24 14:52:47 +08:00
}