From d9e359d481cc523be1732d78c44f348c42e6d291 Mon Sep 17 00:00:00 2001 From: Jin Hai Date: Fri, 24 Jul 2026 20:19:41 +0800 Subject: [PATCH] Go: add context (#17354) Signed-off-by: Jin Hai --- internal/dao/file.go | 156 ++++++++-------- internal/handler/file.go | 25 ++- internal/handler/file_commit.go | 74 ++++---- internal/handler/file_commit_test.go | 63 +++---- internal/handler/skill_search.go | 6 +- .../ingestion/component/document_storage.go | 2 +- internal/ingestion/component/extractor_tag.go | 2 +- .../service/document/document_artifact.go | 2 +- internal/service/document/document_crud.go | 23 ++- .../document/document_dataset_update.go | 5 +- internal/service/document/document_parse.go | 2 +- internal/service/document/document_test.go | 21 ++- internal/service/document/document_upload.go | 36 ++-- internal/service/document/file2document.go | 21 ++- internal/service/file/file.go | 2 +- internal/service/file/file_commit.go | 42 +++-- internal/service/file/file_content.go | 8 +- internal/service/file/file_delete.go | 19 +- internal/service/file/file_folder.go | 172 ++++++++++-------- internal/service/file/file_test.go | 3 +- internal/service/file/file_upload.go | 35 ++-- internal/service/file_permission.go | 5 +- internal/service/oauth_login.go | 6 +- internal/service/skill_indexer.go | 16 +- internal/service/skill_space.go | 43 +++-- 25 files changed, 438 insertions(+), 351 deletions(-) diff --git a/internal/dao/file.go b/internal/dao/file.go index 7038c4707c..9d0ddb71cd 100644 --- a/internal/dao/file.go +++ b/internal/dao/file.go @@ -17,11 +17,14 @@ package dao import ( + "context" "fmt" "log" "ragflow/internal/entity" "ragflow/internal/utility" "strings" + + "gorm.io/gorm" ) // FileDAO file data access object @@ -33,9 +36,9 @@ func NewFileDAO() *FileDAO { } // GetByID gets file by ID -func (dao *FileDAO) GetByID(id string) (*entity.File, error) { +func (dao *FileDAO) GetByID(ctx context.Context, db *gorm.DB, id string) (*entity.File, error) { var file entity.File - err := DB.Where("id = ?", id).First(&file).Error + err := db.WithContext(ctx).Where("id = ?", id).First(&file).Error if err != nil { return nil, err } @@ -43,11 +46,11 @@ func (dao *FileDAO) GetByID(id string) (*entity.File, error) { } // GetByPfID gets files by parent folder ID with pagination and filtering -func (dao *FileDAO) GetByPfID(tenantID, pfID string, page, pageSize int, orderby string, desc bool, keywords string) ([]*entity.File, int64, error) { +func (dao *FileDAO) GetByPfID(ctx context.Context, db *gorm.DB, tenantID, pfID string, page, pageSize int, orderBy string, desc bool, keywords string) ([]*entity.File, int64, error) { var files []*entity.File var total int64 - query := DB.Model(&entity.File{}). + query := db.WithContext(ctx).Model(&entity.File{}). Where("tenant_id = ? AND parent_id = ? AND id != ?", tenantID, pfID, pfID) // Apply keyword filter @@ -65,7 +68,7 @@ func (dao *FileDAO) GetByPfID(tenantID, pfID string, page, pageSize int, orderby if desc { orderDirection = "DESC" } - query = query.Order(orderby + " " + orderDirection) + query = query.Order(orderBy + " " + orderDirection) // Apply pagination if page > 0 && pageSize > 0 { @@ -83,9 +86,9 @@ func (dao *FileDAO) GetByPfID(tenantID, pfID string, page, pageSize int, orderby } // GetRootFolder gets or creates root folder for tenant -func (dao *FileDAO) GetRootFolder(tenantID string) (*entity.File, error) { +func (dao *FileDAO) GetRootFolder(ctx context.Context, db *gorm.DB, tenantID string) (*entity.File, error) { var file entity.File - err := DB.Where("tenant_id = ? AND parent_id = id", tenantID).First(&file).Error + err := db.WithContext(ctx).Where("tenant_id = ? AND parent_id = id", tenantID).First(&file).Error if err == nil { return &file, nil } @@ -103,22 +106,22 @@ func (dao *FileDAO) GetRootFolder(tenantID string) (*entity.File, error) { } file.SourceType = "" - if err = DB.Create(&file).Error; err != nil { + if err = db.WithContext(ctx).Create(&file).Error; err != nil { return nil, err } return &file, nil } // GetParentFolder gets parent folder of a file -func (dao *FileDAO) GetParentFolder(fileID string) (*entity.File, error) { +func (dao *FileDAO) GetParentFolder(ctx context.Context, db *gorm.DB, fileID string) (*entity.File, error) { var file entity.File - err := DB.Where("id = ?", fileID).First(&file).Error + err := db.WithContext(ctx).Where("id = ?", fileID).First(&file).Error if err != nil { return nil, err } var parentFile entity.File - err = DB.Where("id = ?", file.ParentID).First(&parentFile).Error + err = db.WithContext(ctx).Where("id = ?", file.ParentID).First(&parentFile).Error if err != nil { return nil, err } @@ -126,20 +129,20 @@ func (dao *FileDAO) GetParentFolder(fileID string) (*entity.File, error) { } // ListByParentID lists all files by parent ID (including subfolders) -func (dao *FileDAO) ListByParentID(parentID string) ([]*entity.File, error) { +func (dao *FileDAO) ListByParentID(ctx context.Context, db *gorm.DB, parentID string) ([]*entity.File, error) { var files []*entity.File - err := DB.Where("parent_id = ? AND id != ?", parentID, parentID).Find(&files).Error + err := db.WithContext(ctx).Where("parent_id = ? AND id != ?", parentID, parentID).Find(&files).Error return files, err } // GetFolderSize calculates folder size recursively -func (dao *FileDAO) GetFolderSize(folderID string) (int64, error) { +func (dao *FileDAO) GetFolderSize(ctx context.Context, db *gorm.DB, folderID string) (int64, error) { var size int64 var dfs func(parentID string) error dfs = func(parentID string) error { var files []*entity.File - if err := DB.Select("id", "size", "type"). + if err := db.WithContext(ctx).Select("id", "size", "type"). Where("parent_id = ? AND id != ?", parentID, parentID). Find(&files).Error; err != nil { return err @@ -163,22 +166,22 @@ func (dao *FileDAO) GetFolderSize(folderID string) (int64, error) { } // HasChildFolder checks if folder has child folders -func (dao *FileDAO) HasChildFolder(folderID string) (bool, error) { +func (dao *FileDAO) HasChildFolder(ctx context.Context, db *gorm.DB, folderID string) (bool, error) { var count int64 - err := DB.Model(&entity.File{}). + err := db.WithContext(ctx).Model(&entity.File{}). Where("parent_id = ? AND id != ? AND type = ?", folderID, folderID, "folder"). Count(&count).Error return count > 0, err } // GetAllParentFolders gets all parent folders in path (from current to root) -func (dao *FileDAO) GetAllParentFolders(startID string) ([]*entity.File, error) { +func (dao *FileDAO) GetAllParentFolders(ctx context.Context, db *gorm.DB, startID string) ([]*entity.File, error) { var parentFolders []*entity.File currentID := startID for currentID != "" { var file entity.File - err := DB.Where("id = ?", currentID).First(&file).Error + err := db.WithContext(ctx).Where("id = ?", currentID).First(&file).Error if err != nil { return nil, err } @@ -196,72 +199,72 @@ func (dao *FileDAO) GetAllParentFolders(startID string) ([]*entity.File, error) } // Create creates a new file -func (dao *FileDAO) Create(file *entity.File) error { - return DB.Create(file).Error +func (dao *FileDAO) Create(ctx context.Context, db *gorm.DB, file *entity.File) error { + return db.WithContext(ctx).Create(file).Error } // UpdateByID updates a file by ID -func (dao *FileDAO) UpdateByID(id string, updates map[string]interface{}) error { - return DB.Model(&entity.File{}).Where("id = ?", id).Updates(updates).Error +func (dao *FileDAO) UpdateByID(ctx context.Context, db *gorm.DB, id string, updates map[string]interface{}) error { + return db.WithContext(ctx).Model(&entity.File{}).Where("id = ?", id).Updates(updates).Error } // DeleteByTenantID deletes all files by tenant ID (hard delete) -func (dao *FileDAO) DeleteByTenantID(tenantID string) (int64, error) { - result := DB.Unscoped().Where("tenant_id = ?", tenantID).Delete(&entity.File{}) +func (dao *FileDAO) DeleteByTenantID(ctx context.Context, db *gorm.DB, tenantID string) (int64, error) { + result := db.WithContext(ctx).Unscoped().Where("tenant_id = ?", tenantID).Delete(&entity.File{}) return result.RowsAffected, result.Error } // DeleteByIDs deletes files by IDs (hard delete) -func (dao *FileDAO) DeleteByIDs(ids []string) (int64, error) { +func (dao *FileDAO) DeleteByIDs(ctx context.Context, db *gorm.DB, ids []string) (int64, error) { if len(ids) == 0 { return 0, nil } - result := DB.Unscoped().Where("id IN ?", ids).Delete(&entity.File{}) + result := db.WithContext(ctx).Unscoped().Where("id IN ?", ids).Delete(&entity.File{}) return result.RowsAffected, result.Error } // GetAllIDsByTenantID gets all file IDs by tenant ID -func (dao *FileDAO) GetAllIDsByTenantID(tenantID string) ([]string, error) { +func (dao *FileDAO) GetAllIDsByTenantID(ctx context.Context, db *gorm.DB, tenantID string) ([]string, error) { var ids []string - err := DB.Model(&entity.File{}).Where("tenant_id = ?", tenantID).Pluck("id", &ids).Error + err := db.WithContext(ctx).Model(&entity.File{}).Where("tenant_id = ?", tenantID).Pluck("id", &ids).Error return ids, err } // GetByIDs gets files by multiple IDs -func (dao *FileDAO) GetByIDs(ids []string) ([]*entity.File, error) { +func (dao *FileDAO) GetByIDs(ctx context.Context, db *gorm.DB, ids []string) ([]*entity.File, error) { var files []*entity.File if len(ids) == 0 { return files, nil } - err := DB.Where("id IN ?", ids).Find(&files).Error + err := db.WithContext(ctx).Where("id IN ?", ids).Find(&files).Error return files, err } // ListAllFilesByParentID lists all files by parent folder ID -func (dao *FileDAO) ListAllFilesByParentID(parentID string) ([]*entity.File, error) { +func (dao *FileDAO) ListAllFilesByParentID(ctx context.Context, db *gorm.DB, parentID string) ([]*entity.File, error) { var files []*entity.File - err := DB.Where("parent_id = ? AND id != ?", parentID, parentID).Find(&files).Error + err := db.WithContext(ctx).Where("parent_id = ? AND id != ?", parentID, parentID).Find(&files).Error return files, err } // ListNonFolderByParentID lists non-folder files directly under a parent folder. -func (dao *FileDAO) ListNonFolderByParentID(parentID string) ([]*entity.File, error) { +func (dao *FileDAO) ListNonFolderByParentID(ctx context.Context, db *gorm.DB, parentID string) ([]*entity.File, error) { var files []*entity.File - err := DB.Where("parent_id = ? AND id != ? AND type != ?", parentID, parentID, "folder").Find(&files).Error + err := db.WithContext(ctx).Where("parent_id = ? AND id != ? AND type != ?", parentID, parentID, "folder").Find(&files).Error return files, err } // ListFolderByParentID lists sub-folders directly under a parent folder. -func (dao *FileDAO) ListFolderByParentID(parentID string) ([]*entity.File, error) { +func (dao *FileDAO) ListFolderByParentID(ctx context.Context, db *gorm.DB, parentID string) ([]*entity.File, error) { var files []*entity.File - err := DB.Where("parent_id = ? AND type = ?", parentID, "folder").Find(&files).Error + err := db.WithContext(ctx).Where("parent_id = ? AND type = ?", parentID, "folder").Find(&files).Error return files, err } // GetByParentIDAndName gets file by parent folder ID and name -func (dao *FileDAO) GetByParentIDAndName(parentID, name string) (*entity.File, error) { +func (dao *FileDAO) GetByParentIDAndName(ctx context.Context, db *gorm.DB, parentID, name string) (*entity.File, error) { var file entity.File - err := DB.Where("parent_id = ? AND name = ?", parentID, name).First(&file).Error + err := db.WithContext(ctx).Where("parent_id = ? AND name = ?", parentID, name).First(&file).Error if err != nil { return nil, err } @@ -269,20 +272,20 @@ func (dao *FileDAO) GetByParentIDAndName(parentID, name string) (*entity.File, e } // GetIDListByID recursively gets list of file IDs by traversing folder structure -func (dao *FileDAO) GetIDListByID(id string, names []string, count int, res []string) ([]string, error) { +func (dao *FileDAO) GetIDListByID(ctx context.Context, db *gorm.DB, id string, names []string, count int, res []string) ([]string, error) { if count < len(names) { - file, err := dao.GetByParentIDAndName(id, names[count]) + file, err := dao.GetByParentIDAndName(ctx, db, id, names[count]) if err != nil { return res, nil } res = append(res, file.ID) - return dao.GetIDListByID(file.ID, names, count+1, res) + return dao.GetIDListByID(ctx, db, file.ID, names, count+1, res) } return res, nil } // CreateFolder creates a folder in the database -func (dao *FileDAO) CreateFolder(parentID, tenantID, name, fileType string) (*entity.File, error) { +func (dao *FileDAO) CreateFolder(ctx context.Context, db *gorm.DB, parentID, tenantID, name, fileType string) (*entity.File, error) { file := &entity.File{ ID: utility.GenerateToken(), ParentID: parentID, @@ -293,21 +296,21 @@ func (dao *FileDAO) CreateFolder(parentID, tenantID, name, fileType string) (*en Size: 0, SourceType: "", } - if err := DB.Create(file).Error; err != nil { + if err := db.WithContext(ctx).Create(file).Error; err != nil { return nil, err } return file, nil } // Insert inserts a new file record -func (dao *FileDAO) Insert(file *entity.File) error { - return DB.Create(file).Error +func (dao *FileDAO) Insert(ctx context.Context, db *gorm.DB, file *entity.File) error { + return db.WithContext(ctx).Create(file).Error } // IsParentFolderExist checks if parent folder exists -func (dao *FileDAO) IsParentFolderExist(parentID string) bool { +func (dao *FileDAO) IsParentFolderExist(ctx context.Context, db *gorm.DB, parentID string) bool { var count int64 - err := DB.Model(&entity.File{}).Where("id = ?", parentID).Count(&count).Error + err := db.WithContext(ctx).Model(&entity.File{}).Where("id = ?", parentID).Count(&count).Error if err != nil || count == 0 { return false } @@ -315,9 +318,9 @@ func (dao *FileDAO) IsParentFolderExist(parentID string) bool { } // Query retrieves files by conditions -func (dao *FileDAO) Query(name string, parentID string, tenantID string) []*entity.File { +func (dao *FileDAO) Query(ctx context.Context, db *gorm.DB, name string, parentID string, tenantID string) ([]*entity.File, error) { var files []*entity.File - query := DB.Model(&entity.File{}) + query := db.WithContext(ctx).Model(&entity.File{}) if name != "" { query = query.Where("name = ?", name) } @@ -327,19 +330,21 @@ func (dao *FileDAO) Query(name string, parentID string, tenantID string) []*enti if tenantID != "" { query = query.Where("tenant_id = ?", tenantID) } - query.Find(&files) - return files + if err := query.Find(&files).Error; err != nil { + return nil, err + } + return files, nil } // Delete deletes a file by ID (hard delete) -func (dao *FileDAO) Delete(id string) error { - return DB.Unscoped().Where("id = ?", id).Delete(&entity.File{}).Error +func (dao *FileDAO) Delete(ctx context.Context, db *gorm.DB, id string) error { + return db.WithContext(ctx).Unscoped().Where("id = ?", id).Delete(&entity.File{}).Error } // GetDatasetIDByFileID gets dataset ID by file ID -func (dao *FileDAO) GetDatasetIDByFileID(fileID string) ([]string, error) { +func (dao *FileDAO) GetDatasetIDByFileID(ctx context.Context, db *gorm.DB, fileID string) ([]string, error) { var datasetIDs []string - rows, err := DB.Model(&entity.File{}). + rows, err := db.WithContext(ctx).Model(&entity.File{}). Select("knowledgebase.id"). Joins("JOIN file2document ON file2document.file_id = ?", fileID). Joins("JOIN document ON document.id = file2document.document_id"). @@ -366,16 +371,16 @@ func (dao *FileDAO) GetDatasetIDByFileID(fileID string) ([]string, error) { // reparenting any child records to the kept folder, then hard-deleting // the duplicate row. This prevents orphaned children when cleaning up // duplicates created by race conditions. -func reparentAndDeleteFolder(dupID, keepID string) error { +func reparentAndDeleteFolder(ctx context.Context, db *gorm.DB, dupID, keepID string) error { // Reparent any child files/folders from the duplicate to the kept folder - if err := DB.Model(&entity.File{}). + if err := db.WithContext(ctx).Model(&entity.File{}). Where("parent_id = ?", dupID). Update("parent_id", keepID).Error; err != nil { return fmt.Errorf("failed to reparent children from %s to %s: %w", dupID, keepID, err) } // Hard-delete the duplicate folder row - if err := DB.Unscoped().Where("id = ?", dupID).Delete(&entity.File{}).Error; err != nil { + if err := db.WithContext(ctx).Unscoped().Where("id = ?", dupID).Delete(&entity.File{}).Error; err != nil { return fmt.Errorf("failed to delete duplicate folder %s: %w", dupID, err) } @@ -389,9 +394,9 @@ const DatasetFolderName = ".knowledgebase" // This matches Python's FileService.init_dataset_docs method. // Deduplicates duplicate entries that may have been created by // concurrent race conditions (TOCTOU). -func (dao *FileDAO) InitDatasetDocs(rootID, tenantID string, file2DocumentDAO *File2DocumentDAO) error { +func (dao *FileDAO) InitDatasetDocs(ctx context.Context, db *gorm.DB, rootID, tenantID string, file2DocumentDAO *File2DocumentDAO) error { var existing []*entity.File - err := DB.Where("name = ? AND parent_id = ? AND tenant_id = ?", DatasetFolderName, rootID, tenantID). + err := db.WithContext(ctx).Where("name = ? AND parent_id = ? AND tenant_id = ?", DatasetFolderName, rootID, tenantID). Order("create_time ASC"). Find(&existing).Error if err != nil { @@ -404,7 +409,7 @@ func (dao *FileDAO) InitDatasetDocs(rootID, tenantID string, file2DocumentDAO *F len(existing), DatasetFolderName, rootID) keepID := existing[0].ID for _, dup := range existing[1:] { - if err := reparentAndDeleteFolder(dup.ID, keepID); err != nil { + if err := reparentAndDeleteFolder(ctx, db, dup.ID, keepID); err != nil { log.Printf("[ERROR] Failed to deduplicate folder %s: %v", dup.ID, err) } } @@ -412,13 +417,13 @@ func (dao *FileDAO) InitDatasetDocs(rootID, tenantID string, file2DocumentDAO *F return nil } - datasetFolder, err := dao.newAFileFromDataset(tenantID, DatasetFolderName, rootID) + datasetFolder, err := dao.newAFileFromDataset(ctx, db, tenantID, DatasetFolderName, rootID) if err != nil { return err } var datasets []entity.Knowledgebase - err = DB.Select("id", "name"). + err = db.WithContext(ctx).Select("id", "name"). Where("tenant_id = ?", tenantID). Find(&datasets).Error if err != nil { @@ -426,19 +431,20 @@ func (dao *FileDAO) InitDatasetDocs(rootID, tenantID string, file2DocumentDAO *F } for _, ds := range datasets { - datasetFolderForDataset, err := dao.newAFileFromDataset(tenantID, ds.Name, datasetFolder.ID) + var datasetFolderForDataset *entity.File + datasetFolderForDataset, err = dao.newAFileFromDataset(ctx, db, tenantID, ds.Name, datasetFolder.ID) if err != nil { continue } var documents []entity.Document - err = DB.Where("kb_id = ?", ds.ID).Find(&documents).Error + err = db.WithContext(ctx).Where("kb_id = ?", ds.ID).Find(&documents).Error if err != nil { continue } for _, doc := range documents { - if err := dao.addFileFromKB(&doc, datasetFolderForDataset.ID, tenantID, file2DocumentDAO); err != nil { + if err = dao.addFileFromKB(ctx, db, &doc, datasetFolderForDataset.ID, tenantID, file2DocumentDAO); err != nil { return err } } @@ -449,9 +455,9 @@ func (dao *FileDAO) InitDatasetDocs(rootID, tenantID string, file2DocumentDAO *F // newAFileFromDataset creates a new file from knowledgebase, or returns the existing one. // Deduplicates duplicate entries that may have been created by race conditions. -func (dao *FileDAO) newAFileFromDataset(tenantID, name, parentID string) (*entity.File, error) { +func (dao *FileDAO) newAFileFromDataset(ctx context.Context, db *gorm.DB, tenantID, name, parentID string) (*entity.File, error) { var existingFiles []*entity.File - err := DB.Where("tenant_id = ? AND parent_id = ? AND name = ?", tenantID, parentID, name).Order("create_time ASC").Find(&existingFiles).Error + err := db.WithContext(ctx).Where("tenant_id = ? AND parent_id = ? AND name = ?", tenantID, parentID, name).Order("create_time ASC").Find(&existingFiles).Error if err != nil { return nil, err } @@ -462,7 +468,7 @@ func (dao *FileDAO) newAFileFromDataset(tenantID, name, parentID string) (*entit len(existingFiles), name, parentID) keepID := existingFiles[0].ID for _, dup := range existingFiles[1:] { - if err := reparentAndDeleteFolder(dup.ID, keepID); err != nil { + if err = reparentAndDeleteFolder(ctx, db, dup.ID, keepID); err != nil { log.Printf("[ERROR] Failed to deduplicate file entry %s: %v", dup.ID, err) } } @@ -482,16 +488,16 @@ func (dao *FileDAO) newAFileFromDataset(tenantID, name, parentID string) (*entit SourceType: "knowledgebase", } - if err = DB.Create(file).Error; err != nil { + if err = db.WithContext(ctx).Create(file).Error; err != nil { return nil, err } return file, nil } // addFileFromKB adds a file record from knowledgebase document -func (dao *FileDAO) addFileFromKB(doc *entity.Document, datasetFolderID, tenantID string, file2DocumentDAO *File2DocumentDAO) error { +func (dao *FileDAO) addFileFromKB(ctx context.Context, db *gorm.DB, doc *entity.Document, datasetFolderID, tenantID string, file2DocumentDAO *File2DocumentDAO) error { var f2dCount int64 - err := DB.Model(&entity.File2Document{}). + err := db.WithContext(ctx).Model(&entity.File2Document{}). Where("document_id = ?", doc.ID). Count(&f2dCount).Error if err != nil { @@ -525,7 +531,7 @@ func (dao *FileDAO) addFileFromKB(doc *entity.Document, datasetFolderID, tenantI SourceType: "knowledgebase", } - if err = DB.Create(file).Error; err != nil { + if err = db.WithContext(ctx).Create(file).Error; err != nil { return err } @@ -536,7 +542,7 @@ func (dao *FileDAO) addFileFromKB(doc *entity.Document, datasetFolderID, tenantI DocumentID: &doc.ID, } - if err = DB.Create(f2d).Error; err != nil { + if err = db.WithContext(ctx).Create(f2d).Error; err != nil { return err } diff --git a/internal/handler/file.go b/internal/handler/file.go index d830339441..c16618517e 100644 --- a/internal/handler/file.go +++ b/internal/handler/file.go @@ -108,7 +108,8 @@ func (h *FileHandler) ListFiles(c *gin.Context) { desc = descStr != "false" } - result, err := h.fileService.ListFiles(userID, parentID, page, pageSize, orderby, desc, keywords) + ctx := c.Request.Context() + result, err := h.fileService.ListFiles(ctx, userID, parentID, page, pageSize, orderby, desc, keywords) if err != nil { jsonInternalError(c, err) return @@ -133,8 +134,9 @@ func (h *FileHandler) GetRootFolder(c *gin.Context) { } userID := user.ID + ctx := c.Request.Context() // Get root folder - rootFolder, err := h.fileService.GetRootFolder(userID) + rootFolder, err := h.fileService.GetRootFolder(ctx, userID) if err != nil { jsonInternalError(c, err) return @@ -167,8 +169,9 @@ func (h *FileHandler) GetParentFolder(c *gin.Context) { return } + ctx := c.Request.Context() // Get parent folder with permission check - parentFolder, err := h.fileService.GetParentFolder(userID, fileID) + parentFolder, err := h.fileService.GetParentFolder(ctx, userID, fileID) if err != nil { jsonInternalError(c, err) return @@ -201,8 +204,9 @@ func (h *FileHandler) GetAllParentFolders(c *gin.Context) { return } + ctx := c.Request.Context() // Get all parent folders with permission check - parentFolders, err := h.fileService.GetAllParentFolders(userID, fileID) + parentFolders, err := h.fileService.GetAllParentFolders(ctx, userID, fileID) if err != nil { jsonInternalError(c, err) return @@ -234,8 +238,9 @@ func (h *FileHandler) GetFileAncestors(c *gin.Context) { return } + ctx := c.Request.Context() // Get all parent folders with permission check - parentFolders, err := h.fileService.GetAllParentFolders(userID, fileID) + parentFolders, err := h.fileService.GetAllParentFolders(ctx, userID, fileID) if err != nil { jsonInternalError(c, err) return @@ -271,6 +276,7 @@ func (h *FileHandler) UploadFile(c *gin.Context) { userID := user.ID contentType := c.ContentType() + ctx := c.Request.Context() if strings.Contains(contentType, "multipart/form-data") { if err := c.Request.ParseMultipartForm(32 << 20); err != nil { @@ -285,7 +291,7 @@ func (h *FileHandler) UploadFile(c *gin.Context) { } parentID := c.PostForm("parent_id") if parentID == "" { - rootFolder, err := h.fileService.GetRootFolder(userID) + rootFolder, err := h.fileService.GetRootFolder(ctx, userID) if err != nil { jsonInternalError(c, err) return @@ -326,7 +332,7 @@ func (h *FileHandler) UploadFile(c *gin.Context) { parentID := req.ParentID if parentID == "" { - rootFolder, err := h.fileService.GetRootFolder(userID) + rootFolder, err := h.fileService.GetRootFolder(ctx, userID) if err != nil { jsonInternalError(c, err) return @@ -334,7 +340,7 @@ func (h *FileHandler) UploadFile(c *gin.Context) { parentID = rootFolder["id"].(string) } - result, err := h.fileService.CreateFolder(userID, req.Name, parentID, req.Type) + result, err := h.fileService.CreateFolder(ctx, userID, req.Name, parentID, req.Type) if err != nil { common.ErrorWithCode(c, common.CodeBadRequest, err.Error()) return @@ -461,8 +467,9 @@ func (h *FileHandler) Download(c *gin.Context) { return } + ctx := c.Request.Context() // Get file metadata and check permission - file, err := h.fileService.GetFileContent(userID, fileID) + file, err := h.fileService.GetFileContent(ctx, userID, fileID) if err != nil { common.ResponseWithCodeData(c, common.CodeUnauthorized, nil, err.Error()) return diff --git a/internal/handler/file_commit.go b/internal/handler/file_commit.go index 4866bd2464..071fec237e 100644 --- a/internal/handler/file_commit.go +++ b/internal/handler/file_commit.go @@ -17,6 +17,7 @@ package handler import ( + "context" "fmt" "ragflow/internal/common" "ragflow/internal/dao" @@ -28,15 +29,15 @@ import ( // fileCommitService is the consumer-side interface for FileCommitHandler's service dependency. type fileCommitService interface { - CreateCommit(folderID, authorID, message string, changes []entity.FileChange) (*entity.FileCommit, error) - ListCommits(folderID string, page, pageSize int, orderBy string, desc bool) ([]*entity.FileCommit, int64, error) - GetCommit(commitID string) (*entity.FileCommit, error) - ListCommitFiles(commitID string) ([]*entity.FileCommitItem, error) - DiffCommits(fromID, toID string) ([]entity.DiffEntry, error) - GetUncommittedChanges(folderID string) ([]entity.DiffEntry, error) - GetCommitTree(commitID string) (map[string]interface{}, error) - GetCommitFileContent(folderID, commitID, fileID string) ([]byte, error) - GetFileVersionHistory(fileID string) ([]entity.VersionEntry, error) + CreateCommit(ctx context.Context, folderID, authorID, message string, changes []entity.FileChange) (*entity.FileCommit, error) + ListCommits(ctx context.Context, folderID string, page, pageSize int, orderBy string, desc bool) ([]*entity.FileCommit, int64, error) + GetCommit(ctx context.Context, commitID string) (*entity.FileCommit, error) + ListCommitFiles(ctx context.Context, commitID string) ([]*entity.FileCommitItem, error) + DiffCommits(ctx context.Context, fromID, toID string) ([]entity.DiffEntry, error) + GetUncommittedChanges(ctx context.Context, folderID string) ([]entity.DiffEntry, error) + GetCommitTree(ctx context.Context, commitID string) (map[string]interface{}, error) + GetCommitFileContent(ctx context.Context, folderID, commitID, fileID string) ([]byte, error) + GetFileVersionHistory(ctx context.Context, fileID string) ([]entity.VersionEntry, error) } // FileCommitHandler file commit handler @@ -57,10 +58,10 @@ func NewFileCommitHandler(commitService fileCommitService) *FileCommitHandler { // ResolveFolderID resolves a resource ID (dataset/memory/skill) to its folder_id. // entityType is the plural resource name (e.g. "datasets", "memories", "skills"). -func (h *FileCommitHandler) ResolveFolderID(entityType, entityID string) (string, error) { +func (h *FileCommitHandler) ResolveFolderID(ctx context.Context, entityType, entityID string) (string, error) { switch entityType { case "datasets": - return h.resolveDatasetFolderID(entityID) + return h.resolveDatasetFolderID(ctx, entityID) default: return "", fmt.Errorf("unsupported entity type: %s", entityType) } @@ -80,7 +81,8 @@ func CommitFolderResolver(h *FileCommitHandler, entityType, urlParam string) gin c.Abort() return } - folderID, err := h.ResolveFolderID(entityType, id) + ctx := c.Request.Context() + folderID, err := h.ResolveFolderID(ctx, entityType, id) if err != nil { common.ResponseWithCodeData(c, common.CodeNotFound, nil, fmt.Sprintf("%s folder not found", entityType)) c.Abort() @@ -91,12 +93,15 @@ func CommitFolderResolver(h *FileCommitHandler, entityType, urlParam string) gin } } -func (h *FileCommitHandler) resolveDatasetFolderID(datasetID string) (string, error) { +func (h *FileCommitHandler) resolveDatasetFolderID(ctx context.Context, datasetID string) (string, error) { kb, err := h.kbDAO.GetByID(datasetID) if err != nil { return "", err } - files := h.fileDAO.Query(kb.Name, "", kb.TenantID) + files, err := h.fileDAO.Query(ctx, dao.DB, kb.Name, "", kb.TenantID) + if err != nil { + return "", err + } for _, f := range files { if f.SourceType == string(entity.FileSourceKnowledgebase) && f.Type == "folder" && f.TenantID == kb.TenantID { return f.ID, nil @@ -115,8 +120,6 @@ type CreateCommitRequest struct { // @Summary Create Commit // @Description Create a new commit with file changes for a workspace folder // @Tags file_commit -// @Accept json -// @Produce json // @Param folder_id path string true "workspace folder ID" // @Param body body CreateCommitRequest true "commit request" // @Success 200 {object} map[string]interface{} @@ -140,7 +143,8 @@ func (h *FileCommitHandler) CreateCommit(c *gin.Context) { return } - commit, err := h.commitService.CreateCommit(folderID, user.ID, req.Message, req.Files) + ctx := c.Request.Context() + commit, err := h.commitService.CreateCommit(ctx, folderID, user.ID, req.Message, req.Files) if err != nil { jsonInternalError(c, err) return @@ -212,7 +216,8 @@ func (h *FileCommitHandler) ListCommits(c *gin.Context) { desc = descStr != "false" } - commits, total, err := h.commitService.ListCommits(folderID, page, pageSize, orderBy, desc) + ctx := c.Request.Context() + commits, total, err := h.commitService.ListCommits(ctx, folderID, page, pageSize, orderBy, desc) if err != nil { jsonInternalError(c, err) return @@ -267,7 +272,8 @@ func (h *FileCommitHandler) GetCommit(c *gin.Context) { return } - commit, err := h.commitService.GetCommit(commitID) + ctx := c.Request.Context() + commit, err := h.commitService.GetCommit(ctx, commitID) if err != nil { common.ResponseWithCodeData(c, common.CodeNotFound, nil, "Commit not found") return @@ -278,7 +284,7 @@ func (h *FileCommitHandler) GetCommit(c *gin.Context) { return } - items, err := h.commitService.ListCommitFiles(commitID) + items, err := h.commitService.ListCommitFiles(ctx, commitID) if err != nil { items = []*entity.FileCommitItem{} } @@ -324,7 +330,8 @@ func (h *FileCommitHandler) ListCommitFiles(c *gin.Context) { return } - commit, err := h.commitService.GetCommit(commitID) + ctx := c.Request.Context() + commit, err := h.commitService.GetCommit(ctx, commitID) if err != nil { common.ResponseWithCodeData(c, common.CodeNotFound, nil, "Commit not found") return @@ -334,7 +341,7 @@ func (h *FileCommitHandler) ListCommitFiles(c *gin.Context) { return } - items, err := h.commitService.ListCommitFiles(commitID) + items, err := h.commitService.ListCommitFiles(ctx, commitID) if err != nil { jsonInternalError(c, err) return @@ -369,12 +376,13 @@ func (h *FileCommitHandler) DiffCommits(c *gin.Context) { return } - fromCommit, err := h.commitService.GetCommit(fromID) + ctx := c.Request.Context() + fromCommit, err := h.commitService.GetCommit(ctx, fromID) if err != nil { common.ResponseWithCodeData(c, common.CodeNotFound, nil, "Commit not found") return } - toCommit, err := h.commitService.GetCommit(toID) + toCommit, err := h.commitService.GetCommit(ctx, toID) if err != nil { common.ResponseWithCodeData(c, common.CodeNotFound, nil, "Commit not found") return @@ -384,7 +392,7 @@ func (h *FileCommitHandler) DiffCommits(c *gin.Context) { return } - diff, err := h.commitService.DiffCommits(fromID, toID) + diff, err := h.commitService.DiffCommits(ctx, fromID, toID) if err != nil { jsonInternalError(c, err) return @@ -415,7 +423,8 @@ func (h *FileCommitHandler) GetUncommittedChanges(c *gin.Context) { return } - changes, err := h.commitService.GetUncommittedChanges(folderID) + ctx := c.Request.Context() + changes, err := h.commitService.GetUncommittedChanges(ctx, folderID) if err != nil { jsonInternalError(c, err) return @@ -448,7 +457,8 @@ func (h *FileCommitHandler) GetCommitTree(c *gin.Context) { return } - commit, err := h.commitService.GetCommit(commitID) + ctx := c.Request.Context() + commit, err := h.commitService.GetCommit(ctx, commitID) if err != nil { common.ResponseWithCodeData(c, common.CodeNotFound, nil, "Commit not found") return @@ -458,7 +468,7 @@ func (h *FileCommitHandler) GetCommitTree(c *gin.Context) { return } - tree, err := h.commitService.GetCommitTree(commitID) + tree, err := h.commitService.GetCommitTree(ctx, commitID) if err != nil { jsonInternalError(c, err) return @@ -494,7 +504,8 @@ func (h *FileCommitHandler) GetCommitFileContent(c *gin.Context) { return } - commit, err := h.commitService.GetCommit(commitID) + ctx := c.Request.Context() + commit, err := h.commitService.GetCommit(ctx, commitID) if err != nil { common.ResponseWithCodeData(c, common.CodeNotFound, nil, "Commit not found") return @@ -504,7 +515,7 @@ func (h *FileCommitHandler) GetCommitFileContent(c *gin.Context) { return } - content, err := h.commitService.GetCommitFileContent(folderID, commitID, fileID) + content, err := h.commitService.GetCommitFileContent(ctx, folderID, commitID, fileID) if err != nil { common.ResponseWithCodeData(c, common.CodeNotFound, nil, err.Error()) return @@ -535,7 +546,8 @@ func (h *FileCommitHandler) GetFileVersionHistory(c *gin.Context) { return } - versions, err := h.commitService.GetFileVersionHistory(fileID) + ctx := c.Request.Context() + versions, err := h.commitService.GetFileVersionHistory(ctx, fileID) if err != nil { jsonInternalError(c, err) return diff --git a/internal/handler/file_commit_test.go b/internal/handler/file_commit_test.go index 9ccc067513..76987096f5 100644 --- a/internal/handler/file_commit_test.go +++ b/internal/handler/file_commit_test.go @@ -17,6 +17,7 @@ package handler import ( + "context" "encoding/json" "net/http" "net/http/httptest" @@ -31,20 +32,20 @@ import ( // mockFileCommitSvc implements FileCommitServiceInterface for testing type mockFileCommitSvc struct { - createCommitFn func(folderID, authorID, message string, changes []entity.FileChange) (*entity.FileCommit, error) - listCommitsFn func(folderID string, page, pageSize int, orderBy string, desc bool) ([]*entity.FileCommit, int64, error) - getCommitFn func(commitID string) (*entity.FileCommit, error) - listCommitFilesFn func(commitID string) ([]*entity.FileCommitItem, error) - diffCommitsFn func(fromID, toID string) ([]entity.DiffEntry, error) - getUncommittedChangesFn func(folderID string) ([]entity.DiffEntry, error) - getCommitTreeFn func(commitID string) (map[string]interface{}, error) - getCommitFileContentFn func(folderID, commitID, fileID string) ([]byte, error) - getFileVersionHistoryFn func(fileID string) ([]entity.VersionEntry, error) + createCommitFn func(ctx context.Context, folderID, authorID, message string, changes []entity.FileChange) (*entity.FileCommit, error) + listCommitsFn func(ctx context.Context, folderID string, page, pageSize int, orderBy string, desc bool) ([]*entity.FileCommit, int64, error) + getCommitFn func(ctx context.Context, commitID string) (*entity.FileCommit, error) + listCommitFilesFn func(ctx context.Context, commitID string) ([]*entity.FileCommitItem, error) + diffCommitsFn func(ctx context.Context, fromID, toID string) ([]entity.DiffEntry, error) + getUncommittedChangesFn func(ctx context.Context, folderID string) ([]entity.DiffEntry, error) + getCommitTreeFn func(ctx context.Context, commitID string) (map[string]interface{}, error) + getCommitFileContentFn func(ctx context.Context, folderID, commitID, fileID string) ([]byte, error) + getFileVersionHistoryFn func(ctx context.Context, fileID string) ([]entity.VersionEntry, error) } -func (m *mockFileCommitSvc) CreateCommit(folderID, authorID, message string, changes []entity.FileChange) (*entity.FileCommit, error) { +func (m *mockFileCommitSvc) CreateCommit(ctx context.Context, folderID, authorID, message string, changes []entity.FileChange) (*entity.FileCommit, error) { if m.createCommitFn != nil { - return m.createCommitFn(folderID, authorID, message, changes) + return m.createCommitFn(ctx, folderID, authorID, message, changes) } return &entity.FileCommit{ ID: "commit-1", @@ -55,9 +56,9 @@ func (m *mockFileCommitSvc) CreateCommit(folderID, authorID, message string, cha }, nil } -func (m *mockFileCommitSvc) ListCommits(folderID string, page, pageSize int, orderBy string, desc bool) ([]*entity.FileCommit, int64, error) { +func (m *mockFileCommitSvc) ListCommits(ctx context.Context, folderID string, page, pageSize int, orderBy string, desc bool) ([]*entity.FileCommit, int64, error) { if m.listCommitsFn != nil { - return m.listCommitsFn(folderID, page, pageSize, orderBy, desc) + return m.listCommitsFn(ctx, folderID, page, pageSize, orderBy, desc) } now := int64(1718200000000) return []*entity.FileCommit{ @@ -66,59 +67,59 @@ func (m *mockFileCommitSvc) ListCommits(folderID string, page, pageSize int, ord }, 2, nil } -func (m *mockFileCommitSvc) GetCommit(commitID string) (*entity.FileCommit, error) { +func (m *mockFileCommitSvc) GetCommit(ctx context.Context, commitID string) (*entity.FileCommit, error) { if m.getCommitFn != nil { - return m.getCommitFn(commitID) + return m.getCommitFn(ctx, commitID) } return &entity.FileCommit{ID: commitID, FolderID: "folder-1", Message: "test commit", AuthorID: "u1", FileCount: 1}, nil } -func (m *mockFileCommitSvc) ListCommitFiles(commitID string) ([]*entity.FileCommitItem, error) { +func (m *mockFileCommitSvc) ListCommitFiles(ctx context.Context, commitID string) ([]*entity.FileCommitItem, error) { if m.listCommitFilesFn != nil { - return m.listCommitFilesFn(commitID) + return m.listCommitFilesFn(ctx, commitID) } return []*entity.FileCommitItem{ {ID: "i1", CommitID: commitID, FileID: "f1", Operation: "add"}, }, nil } -func (m *mockFileCommitSvc) DiffCommits(fromID, toID string) ([]entity.DiffEntry, error) { +func (m *mockFileCommitSvc) DiffCommits(ctx context.Context, fromID, toID string) ([]entity.DiffEntry, error) { if m.diffCommitsFn != nil { - return m.diffCommitsFn(fromID, toID) + return m.diffCommitsFn(ctx, fromID, toID) } return []entity.DiffEntry{ {FileID: "f1", FileName: "file.txt", Operation: "modify"}, }, nil } -func (m *mockFileCommitSvc) GetUncommittedChanges(folderID string) ([]entity.DiffEntry, error) { +func (m *mockFileCommitSvc) GetUncommittedChanges(ctx context.Context, folderID string) ([]entity.DiffEntry, error) { if m.getUncommittedChangesFn != nil { - return m.getUncommittedChangesFn(folderID) + return m.getUncommittedChangesFn(ctx, folderID) } return []entity.DiffEntry{ {FileID: "f1", FileName: "new.txt", Operation: "add"}, }, nil } -func (m *mockFileCommitSvc) GetCommitTree(commitID string) (map[string]interface{}, error) { +func (m *mockFileCommitSvc) GetCommitTree(ctx context.Context, commitID string) (map[string]interface{}, error) { if m.getCommitTreeFn != nil { - return m.getCommitTreeFn(commitID) + return m.getCommitTreeFn(ctx, commitID) } return map[string]interface{}{ "f1": map[string]interface{}{"name": "file.txt", "hash": "abc123", "size": 100, "status": "1"}, }, nil } -func (m *mockFileCommitSvc) GetCommitFileContent(folderID, commitID, fileID string) ([]byte, error) { +func (m *mockFileCommitSvc) GetCommitFileContent(ctx context.Context, folderID, commitID, fileID string) ([]byte, error) { if m.getCommitFileContentFn != nil { - return m.getCommitFileContentFn(folderID, commitID, fileID) + return m.getCommitFileContentFn(ctx, folderID, commitID, fileID) } return []byte("file content"), nil } -func (m *mockFileCommitSvc) GetFileVersionHistory(fileID string) ([]entity.VersionEntry, error) { +func (m *mockFileCommitSvc) GetFileVersionHistory(ctx context.Context, fileID string) ([]entity.VersionEntry, error) { if m.getFileVersionHistoryFn != nil { - return m.getFileVersionHistoryFn(fileID) + return m.getFileVersionHistoryFn(ctx, fileID) } now := int64(1718200000000) return []entity.VersionEntry{ @@ -159,7 +160,7 @@ func setupFileCommitTestNoAuth() *gin.Engine { func TestFileCommit_CreateCommit_Success(t *testing.T) { r, mock := setupFileCommitTest("user-1") - mock.createCommitFn = func(folderID, authorID, message string, changes []entity.FileChange) (*entity.FileCommit, error) { + mock.createCommitFn = func(ctx context.Context, folderID, authorID, message string, changes []entity.FileChange) (*entity.FileCommit, error) { if folderID != "folder-1" { t.Errorf("expected folder-1, got %s", folderID) } @@ -244,7 +245,7 @@ func TestFileCommit_CreateCommit_InvalidJSON(t *testing.T) { func TestFileCommit_ListCommits_Success(t *testing.T) { r, mock := setupFileCommitTest("user-1") - mock.listCommitsFn = func(folderID string, page, pageSize int, orderBy string, desc bool) ([]*entity.FileCommit, int64, error) { + mock.listCommitsFn = func(ctx context.Context, folderID string, page, pageSize int, orderBy string, desc bool) ([]*entity.FileCommit, int64, error) { if folderID != "folder-1" { t.Errorf("expected folder-1, got %s", folderID) } @@ -292,7 +293,7 @@ func TestFileCommit_GetCommit_Success(t *testing.T) { func TestFileCommit_GetCommit_NotFound(t *testing.T) { r, mock := setupFileCommitTest("user-1") - mock.getCommitFn = func(commitID string) (*entity.FileCommit, error) { + mock.getCommitFn = func(ctx context.Context, commitID string) (*entity.FileCommit, error) { return nil, common.ErrNotFound } @@ -379,7 +380,7 @@ func TestFileCommit_GetCommitTree_Success(t *testing.T) { func TestFileCommit_GetCommitFileContent_Success(t *testing.T) { r, mock := setupFileCommitTest("user-1") - mock.getCommitFileContentFn = func(folderID, commitID, fileID string) ([]byte, error) { + mock.getCommitFileContentFn = func(ctx context.Context, folderID, commitID, fileID string) ([]byte, error) { return []byte("hello world"), nil } diff --git a/internal/handler/skill_search.go b/internal/handler/skill_search.go index 718fb1c173..9ef2a3a2d0 100644 --- a/internal/handler/skill_search.go +++ b/internal/handler/skill_search.go @@ -400,7 +400,8 @@ func (h *SkillSearchHandler) CreateSpace(c *gin.Context) { return } - result, code, err := h.spaceService.CreateSpace(&service.CreateSpaceRequest{ + ctx := c.Request.Context() + result, code, err := h.spaceService.CreateSpace(ctx, &service.CreateSpaceRequest{ TenantID: user.ID, Name: req.Name, Description: req.Description, @@ -486,7 +487,8 @@ func (h *SkillSearchHandler) UpdateSpace(c *gin.Context) { return } - result, code, err := h.spaceService.UpdateSpace(spaceID, user.ID, &service.UpdateSpaceRequest{ + ctx := c.Request.Context() + result, code, err := h.spaceService.UpdateSpace(ctx, spaceID, user.ID, &service.UpdateSpaceRequest{ Name: req.Name, Description: req.Description, EmbdID: req.EmbdID, diff --git a/internal/ingestion/component/document_storage.go b/internal/ingestion/component/document_storage.go index db06c9af77..2ddd3b22f5 100644 --- a/internal/ingestion/component/document_storage.go +++ b/internal/ingestion/component/document_storage.go @@ -97,7 +97,7 @@ func ResolveDocumentStorage(ctx context.Context, docID string) (*DocumentStorage return nil, err } if len(mappings) > 0 && mappings[0].FileID != nil && *mappings[0].FileID != "" { - file, err := dao.NewFileDAO().GetByID(*mappings[0].FileID) + file, err := dao.NewFileDAO().GetByID(ctx, dao.DB, *mappings[0].FileID) if err != nil { return nil, err } diff --git a/internal/ingestion/component/extractor_tag.go b/internal/ingestion/component/extractor_tag.go index 6f476fdfd0..73d692ffdd 100644 --- a/internal/ingestion/component/extractor_tag.go +++ b/internal/ingestion/component/extractor_tag.go @@ -224,7 +224,7 @@ func (c *ExtractorComponent) resolveTagSource(ctx context.Context) (*indexedTagS } func (c *ExtractorComponent) loadTagFileIndexed(ctx context.Context) (*indexedTagSource, bool) { - f, err := dao.NewFileDAO().GetByID(c.Param.TagFileID) + f, err := dao.NewFileDAO().GetByID(ctx, dao.DB, c.Param.TagFileID) if err != nil || f == nil || f.Location == nil || *f.Location == "" { common.Warn(fmt.Sprintf("extractor tags: resolve tag_file_id %q: %v", c.Param.TagFileID, err)) return nil, false diff --git a/internal/service/document/document_artifact.go b/internal/service/document/document_artifact.go index 8f70b14a8b..7240618d84 100644 --- a/internal/service/document/document_artifact.go +++ b/internal/service/document/document_artifact.go @@ -199,7 +199,7 @@ func (s *DocumentService) GetDocumentPreview(ctx context.Context, docID string) return nil, err } - bucket, name, err := s.GetDocumentStorageAddress(doc) + bucket, name, err := s.GetDocumentStorageAddress(ctx, doc) if err != nil { return nil, err } diff --git a/internal/service/document/document_crud.go b/internal/service/document/document_crud.go index a00a564d6c..d8062b48cd 100644 --- a/internal/service/document/document_crud.go +++ b/internal/service/document/document_crud.go @@ -29,7 +29,7 @@ func (s *DocumentService) Accessible(ctx context.Context, docID, userID string) return s.kbDAO.Accessible(doc.KbID, userID) } -func (s *DocumentService) GetDocumentStorageAddress(doc *entity.Document) (string, string, error) { +func (s *DocumentService) GetDocumentStorageAddress(ctx context.Context, doc *entity.Document) (string, string, error) { if doc == nil { return "", "", fmt.Errorf("document is nil") } @@ -43,7 +43,7 @@ func (s *DocumentService) GetDocumentStorageAddress(doc *entity.Document) (strin } if len(mappings) > 0 && mappings[0].FileID != nil { - file, err := fileDAO.GetByID(*mappings[0].FileID) + file, err := fileDAO.GetByID(ctx, dao.DB, *mappings[0].FileID) if err != nil { return "", "", err } @@ -70,7 +70,7 @@ func (s *DocumentService) DownloadDocument(ctx context.Context, datasetID, docID if err != nil || doc.KbID != datasetID { return nil, fmt.Errorf("Document not found!") } - bucket, name, err := s.GetDocumentStorageAddress(doc) + bucket, name, err := s.GetDocumentStorageAddress(ctx, doc) if err != nil { return nil, err } @@ -257,7 +257,11 @@ func (s *DocumentService) deleteDocumentFull(ctx context.Context, docID string) if err = s.deleteDocRecordWithCounters(ctx, doc, kb.ID); err != nil { return err } - s.cleanupFileReferences(docID) + + cleanupCtx := context.WithoutCancel(ctx) + if err = s.cleanupFileReferences(cleanupCtx, docID); err != nil { + return fmt.Errorf("document deleted but file cleanup failed: %w", err) + } return nil } @@ -395,13 +399,14 @@ func (s *DocumentService) rollbackAddFileFromKBError(ctx context.Context, doc *e // the file is a knowledgebase-owned upload (source_type == knowledgebase) and // no other document still references the same file_id. Files linked from file // management are only unlinked — the file record and blob stay intact. -func (s *DocumentService) cleanupFileReferences(docID string) { +func (s *DocumentService) cleanupFileReferences(ctx context.Context, docID string) error { mappings, mapErr := s.file2DocumentDAO.GetByDocumentID(docID) if mapErr != nil { common.Logger.Warn(fmt.Sprintf("cleanupFileReferences: failed to get f2d mappings for %s: %v", docID, mapErr)) + return mapErr } if len(mappings) == 0 { - return + return nil } // Collect unique file_ids @@ -418,6 +423,7 @@ func (s *DocumentService) cleanupFileReferences(docID string) { // Delete all file2document rows for this document if delErr := s.file2DocumentDAO.DeleteByDocumentID(docID); delErr != nil { common.Logger.Warn(fmt.Sprintf("cleanupFileReferences: failed to delete f2d for %s: %v", docID, delErr)) + return delErr } // For each file, only delete the record and blob when it is a @@ -433,7 +439,7 @@ func (s *DocumentService) cleanupFileReferences(docID string) { } fileDAO := dao.NewFileDAO() - file, fErr := fileDAO.GetByID(fileID) + file, fErr := fileDAO.GetByID(ctx, dao.DB, fileID) if fErr != nil || file == nil { common.Logger.Warn(fmt.Sprintf("cleanupFileReferences: file not found %s: %v", fileID, fErr)) continue @@ -441,7 +447,7 @@ func (s *DocumentService) cleanupFileReferences(docID string) { if entity.FileSource(file.SourceType) != entity.FileSourceKnowledgebase { continue // linked from file management — unlink only, keep the file } - if _, delErr := fileDAO.DeleteByIDs([]string{fileID}); delErr != nil { + if _, delErr := fileDAO.DeleteByIDs(ctx, dao.DB, []string{fileID}); delErr != nil { common.Logger.Warn(fmt.Sprintf("cleanupFileReferences: failed to delete file %s: %v", fileID, delErr)) continue // keep the blob so the live file row still has its object } @@ -454,4 +460,5 @@ func (s *DocumentService) cleanupFileReferences(docID string) { } } } + return nil } diff --git a/internal/service/document/document_dataset_update.go b/internal/service/document/document_dataset_update.go index edc0c19881..9c995094c6 100644 --- a/internal/service/document/document_dataset_update.go +++ b/internal/service/document/document_dataset_update.go @@ -349,7 +349,10 @@ func (s *DocumentService) updateDocumentNameOnly(ctx context.Context, doc *entit mappings, err := s.file2DocumentDAO.GetByDocumentID(doc.ID) if err == nil && len(mappings) > 0 && mappings[0].FileID != nil && s.fileDAO != nil { - _ = s.fileDAO.UpdateByID(*mappings[0].FileID, map[string]interface{}{"name": newName}) + err = s.fileDAO.UpdateByID(ctx, dao.DB, *mappings[0].FileID, map[string]interface{}{"name": newName}) + if err != nil { + return fmt.Errorf("file rename failed after document rename: %w", err) + } } if s.docEngine == nil { diff --git a/internal/service/document/document_parse.go b/internal/service/document/document_parse.go index ad5ea0a529..afa8fd93a7 100644 --- a/internal/service/document/document_parse.go +++ b/internal/service/document/document_parse.go @@ -29,7 +29,7 @@ func (s *DocumentService) StartParseDocuments(ctx context.Context, doc *entity.D // Validate storage first so we don't clear prior results and then fail // because the document can't be read, leaving the document with neither // old nor new parse results. - if _, _, err := s.GetDocumentStorageAddress(doc); err != nil { + if _, _, err := s.GetDocumentStorageAddress(ctx, doc); err != nil { return err } diff --git a/internal/service/document/document_test.go b/internal/service/document/document_test.go index 574685ec1b..b9716a9e0c 100644 --- a/internal/service/document/document_test.go +++ b/internal/service/document/document_test.go @@ -664,7 +664,7 @@ func TestDeleteDocumentFull_CleansUpFile2Document(t *testing.T) { } // Verify file record deleted (hard delete) - files, _ := dao.NewFileDAO().GetByIDs([]string{"file-1"}) + files, _ := dao.NewFileDAO().GetByIDs(ctx, db, []string{"file-1"}) if len(files) != 0 { t.Fatalf("expected 0 files, got %d", len(files)) } @@ -702,7 +702,7 @@ func TestDeleteDocumentFull_SharedFilePreserved(t *testing.T) { } // file record should still exist (doc-2 still references it) - files, _ := dao.NewFileDAO().GetByIDs([]string{"file-shared"}) + files, _ := dao.NewFileDAO().GetByIDs(ctx, db, []string{"file-shared"}) if len(files) != 1 { t.Fatalf("expected 1 file record to survive, got %d", len(files)) } @@ -1478,7 +1478,8 @@ func TestCleanupFileReferences_NoMappings(t *testing.T) { svc := testDocumentService(t) // Should not panic with no f2d mappings - svc.cleanupFileReferences("no-mappings") + ctx := t.Context() + svc.cleanupFileReferences(ctx, "no-mappings") } func TestCleanupFileReferences_SingleFileDeleted(t *testing.T) { @@ -1490,7 +1491,8 @@ func TestCleanupFileReferences_SingleFileDeleted(t *testing.T) { insertTestFile2Document(t, "f2d-1", "file-1", "doc-1") svc := testDocumentService(t) - svc.cleanupFileReferences("doc-1") + ctx := t.Context() + svc.cleanupFileReferences(ctx, "doc-1") // f2d gone mappings, _ := dao.NewFile2DocumentDAO().GetByDocumentID("doc-1") @@ -1498,7 +1500,7 @@ func TestCleanupFileReferences_SingleFileDeleted(t *testing.T) { t.Fatalf("expected 0 f2d after cleanup, got %d", len(mappings)) } // file record gone - files, _ := dao.NewFileDAO().GetByIDs([]string{"file-1"}) + files, _ := dao.NewFileDAO().GetByIDs(ctx, db, []string{"file-1"}) if len(files) != 0 { t.Fatalf("expected 0 files after cleanup, got %d", len(files)) } @@ -1514,7 +1516,8 @@ func TestCleanupFileReferences_SharedFileSurvives(t *testing.T) { insertTestFile2Document(t, "f2d-2", "file-shared", "doc-2") svc := testDocumentService(t) - svc.cleanupFileReferences("doc-1") + ctx := t.Context() + svc.cleanupFileReferences(ctx, "doc-1") // f2d for doc-1 gone mappings, _ := dao.NewFile2DocumentDAO().GetByDocumentID("doc-1") @@ -1522,7 +1525,7 @@ func TestCleanupFileReferences_SharedFileSurvives(t *testing.T) { t.Fatalf("expected 0 f2d for doc-1, got %d", len(mappings)) } // file record survives - files, _ := dao.NewFileDAO().GetByIDs([]string{"file-shared"}) + files, _ := dao.NewFileDAO().GetByIDs(ctx, db, []string{"file-shared"}) if len(files) != 1 { t.Fatalf("expected 1 file record, got %d", len(files)) } @@ -1715,7 +1718,7 @@ func TestUpdateDatasetDocumentRenameUpdatesDocumentAndFile(t *testing.T) { if doc.Name == nil || *doc.Name != newName { t.Fatalf("document name = %v, want %q", doc.Name, newName) } - file, _ := dao.NewFileDAO().GetByID("file-1") + file, _ := dao.NewFileDAO().GetByID(ctx, db, "file-1") if file.Name != newName { t.Fatalf("file name = %q, want %q", file.Name, newName) } @@ -2887,7 +2890,7 @@ func TestFileDeleteRemovesLinkedDocument(t *testing.T) { docSvc := testDocumentService(t) fileSvc := file.NewFileService( - func(_ *dao.FileDAO, _ *entity.File, _ string) bool { return true }, + func(_ context.Context, _ *dao.FileDAO, _ *entity.File, _ string) bool { return true }, docSvc, ) diff --git a/internal/service/document/document_upload.go b/internal/service/document/document_upload.go index cc7ecb51dd..c0579fcb03 100644 --- a/internal/service/document/document_upload.go +++ b/internal/service/document/document_upload.go @@ -36,7 +36,7 @@ func (s *DocumentService) UploadLocalDocuments(ctx context.Context, kb *entity.K // Resolve (and create if needed) the dataset's file-manager folder up front. // Without the File / file2document linkage the document list (which inner-joins // file2document + file) would never surface the uploaded files. - kbFolder, err := s.ensureKBFolder(kb, tenantID) + kbFolder, err := s.ensureKBFolder(ctx, kb, tenantID) if err != nil { return nil, []string{err.Error()} } @@ -101,7 +101,7 @@ func (s *DocumentService) UploadLocalDocuments(ctx context.Context, kb *entity.K errMsgs = append(errMsgs, fh.Filename+": "+err.Error()) continue } - if err = s.addFileFromKB(doc, kbFolder.ID, kb.TenantID); err != nil { + if err = s.addFileFromKB(ctx, doc, kbFolder.ID, kb.TenantID); err != nil { // Linkage failed: roll back the document row and blob so the partial // state doesn't leave an invisible (unlisted) document behind. err = s.rollbackAddFileFromKBError(ctx, doc, kb.ID, err) @@ -131,7 +131,7 @@ func (s *DocumentService) UploadEmptyDocument(ctx context.Context, kb *entity.Kn } } - kbFolder, err := s.ensureKBFolder(kb, tenantID) + kbFolder, err := s.ensureKBFolder(ctx, kb, tenantID) if err != nil { return nil, common.CodeServerError, err } @@ -140,7 +140,7 @@ func (s *DocumentService) UploadEmptyDocument(ctx context.Context, kb *entity.Kn if err = s.InsertDocument(doc); err != nil { return nil, common.CodeServerError, err } - if err = s.addFileFromKB(doc, kbFolder.ID, kb.TenantID); err != nil { + if err = s.addFileFromKB(ctx, doc, kbFolder.ID, kb.TenantID); err != nil { return nil, common.CodeServerError, s.rollbackAddFileFromKBError(ctx, doc, kb.ID, err) } return docToRawMap(doc), common.CodeSuccess, nil @@ -149,22 +149,26 @@ func (s *DocumentService) UploadEmptyDocument(ctx context.Context, kb *entity.Kn // ensureKBFolder resolves (creating as needed) the per-dataset file-manager // folder: root -> .knowledgebase -> . Mirrors Python // get_root_folder + get_kb_folder + new_a_file_from_kb. -func (s *DocumentService) ensureKBFolder(kb *entity.Knowledgebase, tenantID string) (*entity.File, error) { - root, err := s.fileDAO.GetRootFolder(tenantID) +func (s *DocumentService) ensureKBFolder(ctx context.Context, kb *entity.Knowledgebase, tenantID string) (*entity.File, error) { + root, err := s.fileDAO.GetRootFolder(ctx, dao.DB, tenantID) if err != nil { return nil, err } - kbRoot, err := s.newAFileFromKB(tenantID, knowledgebaseFolderName, root.ID) + kbRoot, err := s.newAFileFromKB(ctx, tenantID, knowledgebaseFolderName, root.ID) if err != nil { return nil, err } - return s.newAFileFromKB(kb.TenantID, kb.Name, kbRoot.ID) + return s.newAFileFromKB(ctx, kb.TenantID, kb.Name, kbRoot.ID) } // newAFileFromKB returns the existing folder named name under parentID, or // creates it. Mirrors Python FileService.new_a_file_from_kb. -func (s *DocumentService) newAFileFromKB(tenantID, name, parentID string) (*entity.File, error) { - for _, f := range s.fileDAO.Query(name, parentID, tenantID) { +func (s *DocumentService) newAFileFromKB(ctx context.Context, tenantID, name, parentID string) (*entity.File, error) { + existingFolders, err := s.fileDAO.Query(ctx, dao.DB, name, parentID, tenantID) + if err != nil { + return nil, err + } + for _, f := range existingFolders { if f.TenantID == tenantID { return f, nil } @@ -181,7 +185,7 @@ func (s *DocumentService) newAFileFromKB(tenantID, name, parentID string) (*enti Location: &loc, SourceType: string(entity.FileSourceKnowledgebase), } - if err := s.fileDAO.Create(folder); err != nil { + if err := s.fileDAO.Create(ctx, dao.DB, folder); err != nil { return nil, err } return folder, nil @@ -190,7 +194,7 @@ func (s *DocumentService) newAFileFromKB(tenantID, name, parentID string) (*enti // addFileFromKB links a document into the file manager: a File row under the // dataset folder plus a file2document mapping. Mirrors Python // FileService.add_file_from_kb (idempotent on the document mapping). -func (s *DocumentService) addFileFromKB(doc *entity.Document, kbFolderID, tenantID string) error { +func (s *DocumentService) addFileFromKB(ctx context.Context, doc *entity.Document, kbFolderID, tenantID string) error { if existing, err := s.file2DocumentDAO.GetByDocumentID(doc.ID); err == nil && len(existing) > 0 { return nil } @@ -214,7 +218,7 @@ func (s *DocumentService) addFileFromKB(doc *entity.Document, kbFolderID, tenant Location: &loc, SourceType: string(entity.FileSourceKnowledgebase), } - if err := s.fileDAO.Create(file); err != nil { + if err := s.fileDAO.Create(ctx, dao.DB, file); err != nil { return err } docID := doc.ID @@ -223,7 +227,7 @@ func (s *DocumentService) addFileFromKB(doc *entity.Document, kbFolderID, tenant FileID: &fileID, DocumentID: &docID, }); err != nil { - _ = s.fileDAO.Delete(fileID) + _ = s.fileDAO.Delete(ctx, dao.DB, fileID) return err } return nil @@ -235,7 +239,7 @@ func (s *DocumentService) UploadWebDocument(ctx context.Context, kb *entity.Know return nil, common.CodeServerError, fmt.Errorf("storage not initialized") } - kbFolder, err := s.ensureKBFolder(kb, tenantID) + kbFolder, err := s.ensureKBFolder(ctx, kb, tenantID) if err != nil { return nil, common.CodeServerError, err } @@ -279,7 +283,7 @@ func (s *DocumentService) UploadWebDocument(ctx context.Context, kb *entity.Know _ = storageImpl.Remove(kb.ID, location) return nil, common.CodeServerError, err } - if err = s.addFileFromKB(doc, kbFolder.ID, kb.TenantID); err != nil { + if err = s.addFileFromKB(ctx, doc, kbFolder.ID, kb.TenantID); err != nil { err = s.rollbackAddFileFromKBError(ctx, doc, kb.ID, err) _ = storageImpl.Remove(kb.ID, location) return nil, common.CodeServerError, err diff --git a/internal/service/document/file2document.go b/internal/service/document/file2document.go index ee17da3c9a..3d67bb2fb2 100644 --- a/internal/service/document/file2document.go +++ b/internal/service/document/file2document.go @@ -84,7 +84,7 @@ type LinkToDatasetsRequest struct { // handler can map it to a Python-compatible response without leaking internals. func (s *File2DocumentService) LinkToDatasets(ctx context.Context, userID string, req *LinkToDatasetsRequest, mode string) error { // ── 1. Validate files exist ─────────────────────────────────────────────── - files, err := s.fileDAO.GetByIDs(req.FileIDs) + files, err := s.fileDAO.GetByIDs(ctx, dao.DB, req.FileIDs) if err != nil { common.Warn("LinkToDatasets: GetByIDs failed", zap.Error(err)) return ErrLinkInternal @@ -117,7 +117,7 @@ func (s *File2DocumentService) LinkToDatasets(ctx context.Context, userID string for _, id := range req.FileIDs { file := filesSet[id] if file.Type == "folder" { - inner, err := s.getAllInnermostFileIDs(id) + inner, err := s.getAllInnermostFileIDs(ctx, id) if err != nil { common.Warn("LinkToDatasets: folder expansion failed", zap.String("fileID", id), zap.Error(err)) return ErrLinkInternal @@ -131,11 +131,11 @@ func (s *File2DocumentService) LinkToDatasets(ctx context.Context, userID string // ── 4. Validate expanded file permissions ───────────────────────────────── for _, id := range allFileIDs { - file, err := s.fileDAO.GetByID(id) + file, err := s.fileDAO.GetByID(ctx, dao.DB, id) if err != nil || file == nil { return ErrLinkFileNotFound } - if !service.CheckFileTeamPermission(s.fileDAO, file, userID) { + if !service.CheckFileTeamPermission(ctx, s.fileDAO, file, userID) { return ErrLinkNoAuthorization } } @@ -150,7 +150,8 @@ func (s *File2DocumentService) LinkToDatasets(ctx context.Context, userID string // ── 6. Run conversion in background (fire-and-forget) ──────────────────── kbIDs := req.KbIDs go func() { - if err = s.convertFiles(ctx, allFileIDs, kbIDs, userID, mode); err != nil { + newCtx := context.Background() + if err = s.convertFiles(newCtx, allFileIDs, kbIDs, userID, mode); err != nil { common.Warn("file2document.convertFiles failed", zap.Strings("file_ids", allFileIDs), zap.Strings("kb_ids", kbIDs), @@ -211,7 +212,7 @@ func (s *File2DocumentService) convertFiles(ctx context.Context, fileIDs, kbIDs } // Reload the source file. - file, err := s.fileDAO.GetByID(fileID) + file, err := s.fileDAO.GetByID(ctx, dao.DB, fileID) if err != nil || file == nil { continue } @@ -259,7 +260,7 @@ func (s *File2DocumentService) convertFiles(ctx context.Context, fileIDs, kbIDs // InsertDocument creates the row and increments KB doc_num in one // transaction, so a failed insert never leaves a stale counter. - if err := s.documentSvc.InsertDocument(doc); err != nil { + if err = s.documentSvc.InsertDocument(doc); err != nil { common.Warn("convertFiles: InsertDocument failed", zap.String("kbID", kbID), zap.String("fileID", fileID), zap.Error(err)) continue @@ -281,15 +282,15 @@ func (s *File2DocumentService) convertFiles(ctx context.Context, fileIDs, kbIDs // getAllInnermostFileIDs recursively collects all non-folder file IDs under a folder. // Mirrors Python FileService.get_all_innermost_file_ids. -func (s *File2DocumentService) getAllInnermostFileIDs(folderID string) ([]string, error) { - children, err := s.fileDAO.ListByParentID(folderID) +func (s *File2DocumentService) getAllInnermostFileIDs(ctx context.Context, folderID string) ([]string, error) { + children, err := s.fileDAO.ListByParentID(ctx, dao.DB, folderID) if err != nil { return nil, err } var ids []string for _, child := range children { if child.Type == "folder" { - sub, err := s.getAllInnermostFileIDs(child.ID) + sub, err := s.getAllInnermostFileIDs(ctx, child.ID) if err != nil { return nil, err } diff --git a/internal/service/file/file.go b/internal/service/file/file.go index 76d1332f1c..0cae84aa7c 100644 --- a/internal/service/file/file.go +++ b/internal/service/file/file.go @@ -38,7 +38,7 @@ type DocRemover interface { // CheckFilePermFunc is the function signature for file-team permission checks, // injected by the parent adapter so the file subpackage does not need to import // the parent service package. -type CheckFilePermFunc func(fileDAO *dao.FileDAO, file *entity.File, userID string) bool +type CheckFilePermFunc func(ctx context.Context, fileDAO *dao.FileDAO, file *entity.File, userID string) bool // FileService file service type FileService struct { diff --git a/internal/service/file/file_commit.go b/internal/service/file/file_commit.go index c62cfcaf75..727af168a8 100644 --- a/internal/service/file/file_commit.go +++ b/internal/service/file/file_commit.go @@ -17,6 +17,7 @@ package file import ( + "context" "crypto/sha256" "encoding/hex" "encoding/json" @@ -50,7 +51,7 @@ func NewFileCommitService() *FileCommitService { } // CreateCommit creates a new commit for a workspace folder -func (s *FileCommitService) CreateCommit(folderID, authorID, message string, changes []entity.FileChange) (*entity.FileCommit, error) { +func (s *FileCommitService) CreateCommit(ctx context.Context, folderID, authorID, message string, changes []entity.FileChange) (*entity.FileCommit, error) { // 1. Get the latest commit for this folder latestCommit, _ := s.commitDAO.GetLatestByFolderID(folderID) @@ -233,22 +234,22 @@ func (s *FileCommitService) CreateCommit(folderID, authorID, message string, cha } // ListCommits lists commits for a workspace folder with pagination -func (s *FileCommitService) ListCommits(folderID string, page, pageSize int, orderBy string, desc bool) ([]*entity.FileCommit, int64, error) { +func (s *FileCommitService) ListCommits(ctx context.Context, folderID string, page, pageSize int, orderBy string, desc bool) ([]*entity.FileCommit, int64, error) { return s.commitDAO.ListByFolderID(folderID, page, pageSize, orderBy, desc) } // GetCommit gets a single commit by ID -func (s *FileCommitService) GetCommit(commitID string) (*entity.FileCommit, error) { +func (s *FileCommitService) GetCommit(ctx context.Context, commitID string) (*entity.FileCommit, error) { return s.commitDAO.GetByID(commitID) } // ListCommitFiles lists all file change items for a commit -func (s *FileCommitService) ListCommitFiles(commitID string) ([]*entity.FileCommitItem, error) { +func (s *FileCommitService) ListCommitFiles(ctx context.Context, commitID string) ([]*entity.FileCommitItem, error) { return s.commitItemDAO.ListByCommitID(commitID) } // DiffCommits compares two commits and returns the diff -func (s *FileCommitService) DiffCommits(fromID, toID string) ([]entity.DiffEntry, error) { +func (s *FileCommitService) DiffCommits(ctx context.Context, fromID, toID string) ([]entity.DiffEntry, error) { fromItems, err := s.commitItemDAO.ListByCommitID(fromID) if err != nil { return nil, err @@ -348,7 +349,7 @@ func (s *FileCommitService) DiffCommits(fromID, toID string) ([]entity.DiffEntry // GetUncommittedChanges gets uncommitted changes for a workspace folder. // Recursively scans all sub-folders. -func (s *FileCommitService) GetUncommittedChanges(folderID string) ([]entity.DiffEntry, error) { +func (s *FileCommitService) GetUncommittedChanges(ctx context.Context, folderID string) ([]entity.DiffEntry, error) { // Get latest commit tree state latest, err := s.commitDAO.GetLatestByFolderID(folderID) committedFiles := make(map[string]map[string]interface{}) @@ -364,7 +365,7 @@ func (s *FileCommitService) GetUncommittedChanges(folderID string) ([]entity.Dif } // Get all live files recursively under this folder - liveMap := s.collectAllFilesRecursive(folderID) + liveMap := s.collectAllFilesRecursive(ctx, folderID) var changes []entity.DiffEntry processed := make(map[string]bool) @@ -417,17 +418,17 @@ func (s *FileCommitService) GetUncommittedChanges(folderID string) ([]entity.Dif } // collectAllFilesRecursive recursively collects all non-folder files under a folder. -func (s *FileCommitService) collectAllFilesRecursive(folderID string) map[string]*entity.File { +func (s *FileCommitService) collectAllFilesRecursive(ctx context.Context, folderID string) map[string]*entity.File { result := make(map[string]*entity.File) // Direct files (non-folder) - files, _ := s.fileDAO.ListNonFolderByParentID(folderID) + files, _ := s.fileDAO.ListNonFolderByParentID(ctx, dao.DB, folderID) for _, f := range files { result[f.ID] = f } // Sub-folders — recurse - subFolders, _ := s.fileDAO.ListFolderByParentID(folderID) + subFolders, _ := s.fileDAO.ListFolderByParentID(ctx, dao.DB, folderID) for _, sf := range subFolders { - sub := s.collectAllFilesRecursive(sf.ID) + sub := s.collectAllFilesRecursive(ctx, sf.ID) for k, v := range sub { result[k] = v } @@ -436,7 +437,7 @@ func (s *FileCommitService) collectAllFilesRecursive(folderID string) map[string } // GetCommitTree gets the tree state snapshot for a commit as a hierarchical tree. -func (s *FileCommitService) GetCommitTree(commitID string) (map[string]interface{}, error) { +func (s *FileCommitService) GetCommitTree(ctx context.Context, commitID string) (map[string]interface{}, error) { commit, err := s.commitDAO.GetByID(commitID) if err != nil { return nil, err @@ -445,15 +446,15 @@ func (s *FileCommitService) GetCommitTree(commitID string) (map[string]interface return map[string]interface{}{"id": commit.FolderID, "name": "", "type": "folder", "children": []interface{}{}}, nil } var flat map[string]interface{} - if err := json.Unmarshal([]byte(*commit.TreeState), &flat); err != nil { + if err = json.Unmarshal([]byte(*commit.TreeState), &flat); err != nil { return nil, err } - return s.buildHierarchicalTree(flat, commit.FolderID), nil + return s.buildHierarchicalTree(ctx, flat, commit.FolderID), nil } // buildHierarchicalTree builds a recursive tree from a flat tree_state map. // Sub-folder hierarchy is resolved from the File table's parent_id. -func (s *FileCommitService) buildHierarchicalTree(flat map[string]interface{}, rootFolderID string) map[string]interface{} { +func (s *FileCommitService) buildHierarchicalTree(ctx context.Context, flat map[string]interface{}, rootFolderID string) map[string]interface{} { // Collect all unique folder IDs folderIDs := map[string]bool{rootFolderID: true} for _, v := range flat { @@ -470,7 +471,7 @@ func (s *FileCommitService) buildHierarchicalTree(flat map[string]interface{}, r folderParentMap := make(map[string]string) for fid := range folderIDs { if fid != rootFolderID { - if f, err := s.fileDAO.GetByID(fid); err == nil { + if f, err := s.fileDAO.GetByID(ctx, dao.DB, fid); err == nil { folderParentMap[fid] = f.ParentID } } @@ -501,7 +502,7 @@ func (s *FileCommitService) buildHierarchicalTree(flat map[string]interface{}, r var buildNode func(nodeID string) map[string]interface{} buildNode = func(nodeID string) map[string]interface{} { nodeName := nodeID - if f, err := s.fileDAO.GetByID(nodeID); err == nil { + if f, err := s.fileDAO.GetByID(ctx, dao.DB, nodeID); err == nil { nodeName = f.Name } node := map[string]interface{}{ @@ -539,7 +540,7 @@ func (s *FileCommitService) buildHierarchicalTree(flat map[string]interface{}, r } // GetCommitFileContent gets file content as it existed in a given commit -func (s *FileCommitService) GetCommitFileContent(folderID, commitID, fileID string) ([]byte, error) { +func (s *FileCommitService) GetCommitFileContent(ctx context.Context, folderID, commitID, fileID string) ([]byte, error) { _, err := s.commitDAO.GetByID(commitID) if err != nil { return nil, fmt.Errorf("commit not found: %w", err) @@ -577,7 +578,7 @@ func (s *FileCommitService) GetCommitFileContent(folderID, commitID, fileID stri } // GetFileVersionHistory gets version history for a specific file -func (s *FileCommitService) GetFileVersionHistory(fileID string) ([]entity.VersionEntry, error) { +func (s *FileCommitService) GetFileVersionHistory(ctx context.Context, fileID string) ([]entity.VersionEntry, error) { items, err := s.commitItemDAO.ListByFileID(fileID) if err != nil { return nil, err @@ -585,7 +586,8 @@ func (s *FileCommitService) GetFileVersionHistory(fileID string) ([]entity.Versi var versions []entity.VersionEntry for _, item := range items { - commit, err := s.commitDAO.GetByID(item.CommitID) + var commit *entity.FileCommit + commit, err = s.commitDAO.GetByID(item.CommitID) if err != nil { continue } diff --git a/internal/service/file/file_content.go b/internal/service/file/file_content.go index 2385dc115c..d85751b9f4 100644 --- a/internal/service/file/file_content.go +++ b/internal/service/file/file_content.go @@ -15,12 +15,12 @@ import ( // GetFileContent gets file metadata and checks permission for download // Matches Python's file_api_service.get_file_content function -func (s *FileService) GetFileContent(uid, fileID string) (*entity.File, error) { - file, err := s.fileDAO.GetByID(fileID) +func (s *FileService) GetFileContent(ctx context.Context, uid, fileID string) (*entity.File, error) { + file, err := s.fileDAO.GetByID(ctx, dao.DB, fileID) if err != nil || file == nil { return nil, fmt.Errorf("Document not found!") } - if !s.checkFilePerm(s.fileDAO, file, uid) { + if !s.checkFilePerm(ctx, s.fileDAO, file, uid) { return nil, fmt.Errorf("No authorization.") } return file, nil @@ -39,7 +39,7 @@ func (s *FileService) GetStorageAddress(ctx context.Context, fileID string) (*St if f2d[0].FileID == nil { return nil, fmt.Errorf("file_id is nil in file2document mapping") } - file, err := s.fileDAO.GetByID(*f2d[0].FileID) + file, err := s.fileDAO.GetByID(ctx, dao.DB, *f2d[0].FileID) if err != nil || file == nil { return nil, fmt.Errorf("file not found") } diff --git a/internal/service/file/file_delete.go b/internal/service/file/file_delete.go index f39a799bbf..785bbb44d8 100644 --- a/internal/service/file/file_delete.go +++ b/internal/service/file/file_delete.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "ragflow/internal/common" + "ragflow/internal/dao" "ragflow/internal/entity" "ragflow/internal/storage" ) @@ -14,7 +15,7 @@ import ( func (s *FileService) DeleteFiles(ctx context.Context, uid string, fileIDs []string) (bool, string) { for _, fileID := range fileIDs { // 1. Get file - file, err := s.fileDAO.GetByID(fileID) + file, err := s.fileDAO.GetByID(ctx, dao.DB, fileID) if err != nil || file == nil { return false, "File or Folder not found!" } @@ -30,7 +31,7 @@ func (s *FileService) DeleteFiles(ctx context.Context, uid string, fileIDs []str } // 3. Permission check - if !s.checkFilePerm(s.fileDAO, file, uid) { + if !s.checkFilePerm(ctx, s.fileDAO, file, uid) { return false, "No authorization." } @@ -41,11 +42,11 @@ func (s *FileService) DeleteFiles(ctx context.Context, uid string, fileIDs []str // 5. Delete based on type if file.Type == FileTypeFolder { - if err := s.deleteFolderRecursive(ctx, file, uid); err != nil { + if err = s.deleteFolderRecursive(ctx, file, uid); err != nil { return false, fmt.Sprintf("Failed to delete folder: %v", err) } } else { - if err := s.deleteSingleFile(ctx, file); err != nil { + if err = s.deleteSingleFile(ctx, file); err != nil { return false, fmt.Sprintf("Failed to delete file: %v", err) } } @@ -95,7 +96,7 @@ func (s *FileService) deleteSingleFile(ctx context.Context, file *entity.File) e } // 3. Delete file record - if err = s.fileDAO.Delete(file.ID); err != nil { + if err = s.fileDAO.Delete(ctx, dao.DB, file.ID); err != nil { return err } @@ -106,7 +107,7 @@ func (s *FileService) deleteSingleFile(ctx context.Context, file *entity.File) e // Matches Python's _delete_folder_recursive function func (s *FileService) deleteFolderRecursive(ctx context.Context, folder *entity.File, uid string) error { // Get all sub-files - subFiles, err := s.fileDAO.ListByParentID(folder.ID) + subFiles, err := s.fileDAO.ListByParentID(ctx, dao.DB, folder.ID) if err != nil { return err } @@ -114,19 +115,19 @@ func (s *FileService) deleteFolderRecursive(ctx context.Context, folder *entity. for _, subFile := range subFiles { if subFile.Type == FileTypeFolder { // Recursively delete subfolder - if err := s.deleteFolderRecursive(ctx, subFile, uid); err != nil { + if err = s.deleteFolderRecursive(ctx, subFile, uid); err != nil { return err } } else { // Delete single file - if err := s.deleteSingleFile(ctx, subFile); err != nil { + if err = s.deleteSingleFile(ctx, subFile); err != nil { return err } } } // Delete the folder itself - if err := s.fileDAO.Delete(folder.ID); err != nil { + if err = s.fileDAO.Delete(ctx, dao.DB, folder.ID); err != nil { return err } diff --git a/internal/service/file/file_folder.go b/internal/service/file/file_folder.go index d1b0815d6f..2bafc0322a 100644 --- a/internal/service/file/file_folder.go +++ b/internal/service/file/file_folder.go @@ -13,8 +13,8 @@ import ( ) // GetRootFolder gets or creates root folder for tenant -func (s *FileService) GetRootFolder(tenantID string) (map[string]interface{}, error) { - file, err := s.fileDAO.GetRootFolder(tenantID) +func (s *FileService) GetRootFolder(ctx context.Context, tenantID string) (map[string]interface{}, error) { + file, err := s.fileDAO.GetRootFolder(ctx, dao.DB, tenantID) if err != nil { return nil, err } @@ -23,41 +23,41 @@ func (s *FileService) GetRootFolder(tenantID string) (map[string]interface{}, er // ListFiles lists files by parent folder ID (matching Python /files endpoint) // This method includes init_dataset_docs initialization when parent_id is empty -func (s *FileService) ListFiles(tenantID, pfID string, page, pageSize int, orderby string, desc bool, keywords string) (*ListFilesResponse, error) { +func (s *FileService) ListFiles(ctx context.Context, tenantID, pfID string, page, pageSize int, orderby string, desc bool, keywords string) (*ListFilesResponse, error) { // If pfID is empty, get root folder and initialize dataset docs if pfID == "" { - rootFolder, err := s.fileDAO.GetRootFolder(tenantID) + rootFolder, err := s.fileDAO.GetRootFolder(ctx, dao.DB, tenantID) if err != nil { return nil, fmt.Errorf("failed to get root folder: %w", err) } pfID = rootFolder.ID // Initialize dataset docs (matching Python init_knowledgebase_docs logic) - if err := s.initDatasetDocs(pfID, tenantID); err != nil { + if err = s.initDatasetDocs(ctx, pfID, tenantID); err != nil { return nil, fmt.Errorf("failed to initialize dataset docs: %w", err) } // Initialize skills folder (matching Python init_skills_folder logic) - if err := s.initSkillsFolder(pfID, tenantID); err != nil { + if err = s.initSkillsFolder(ctx, pfID, tenantID); err != nil { return nil, fmt.Errorf("failed to initialize skills folder: %w", err) } } // Check if parent folder exists - if _, err := s.fileDAO.GetByID(pfID); err != nil { - return nil, fmt.Errorf("Folder not found!") + if _, err := s.fileDAO.GetByID(ctx, dao.DB, pfID); err != nil { + return nil, fmt.Errorf("folder not found") } // Get files by parent folder ID - files, total, err := s.fileDAO.GetByPfID(tenantID, pfID, page, pageSize, orderby, desc, keywords) + files, total, err := s.fileDAO.GetByPfID(ctx, dao.DB, tenantID, pfID, page, pageSize, orderby, desc, keywords) if err != nil { return nil, err } // Get parent folder - parentFolder, err := s.fileDAO.GetParentFolder(pfID) + parentFolder, err := s.fileDAO.GetParentFolder(ctx, dao.DB, pfID) if err != nil { - return nil, fmt.Errorf("File not found!") + return nil, fmt.Errorf("folder not found") } // Process files to add additional info, deduplicating by ID as a safety net @@ -73,11 +73,11 @@ func (s *FileService) ListFiles(tenantID, pfID string, page, pageSize int, order // If folder, calculate size and check for child folders if file.Type == FileTypeFolder { - folderSize, err := s.fileDAO.GetFolderSize(file.ID) + folderSize, err := s.fileDAO.GetFolderSize(ctx, dao.DB, file.ID) if err == nil { fileInfo.Size = folderSize } - hasChild, err := s.fileDAO.HasChildFolder(file.ID) + hasChild, err := s.fileDAO.HasChildFolder(ctx, dao.DB, file.ID) if err == nil { fileInfo.HasChildFolder = hasChild } @@ -103,15 +103,18 @@ func (s *FileService) ListFiles(tenantID, pfID string, page, pageSize int, order // initDatasetDocs initializes dataset documents for tenant // This matches Python's FileService.init_dataset_docs method -func (s *FileService) initDatasetDocs(rootID, tenantID string) error { - return s.fileDAO.InitDatasetDocs(rootID, tenantID, s.file2DocumentDAO) +func (s *FileService) initDatasetDocs(ctx context.Context, rootID, tenantID string) error { + return s.fileDAO.InitDatasetDocs(ctx, dao.DB, rootID, tenantID, s.file2DocumentDAO) } // initSkillsFolder initializes the skills folder under the root folder. // Deduplicates duplicate entries that may have been created by // concurrent race conditions (TOCTOU). -func (s *FileService) initSkillsFolder(rootID, tenantID string) error { - existing := s.fileDAO.Query(SkillsFolderName, rootID, tenantID) +func (s *FileService) initSkillsFolder(ctx context.Context, rootID, tenantID string) error { + existing, err := s.fileDAO.Query(ctx, dao.DB, SkillsFolderName, rootID, tenantID) + if err != nil { + return err + } if len(existing) > 0 { if len(existing) > 1 { common.Logger.Warn(fmt.Sprintf( @@ -120,12 +123,14 @@ func (s *FileService) initSkillsFolder(rootID, tenantID string) error { )) keepID := existing[0].ID for _, dup := range existing[1:] { - children, _ := s.fileDAO.ListAllFilesByParentID(dup.ID) + children, _ := s.fileDAO.ListAllFilesByParentID(ctx, dao.DB, dup.ID) for _, child := range children { - s.fileDAO.UpdateByID(child.ID, map[string]interface{}{"parent_id": keepID}) + if err := s.fileDAO.UpdateByID(ctx, dao.DB, child.ID, map[string]interface{}{"parent_id": keepID}); err != nil { + common.Logger.Warn(fmt.Sprintf("Failed to update child folder %s: %v", child.ID, err)) + } } - if delErr := s.fileDAO.Delete(dup.ID); delErr != nil { - common.Logger.Warn(fmt.Sprintf("Failed to delete duplicate skills folder %s: %v", dup.ID, delErr)) + if err := s.fileDAO.Delete(ctx, dao.DB, dup.ID); err != nil { + common.Logger.Warn(fmt.Sprintf("Failed to delete duplicate skills folder %s: %v", dup.ID, err)) } } } @@ -142,7 +147,7 @@ func (s *FileService) initSkillsFolder(rootID, tenantID string) error { Size: 0, SourceType: "", } - return s.fileDAO.Insert(folder) + return s.fileDAO.Insert(ctx, dao.DB, folder) } // toFileResponse converts file model to response format @@ -205,20 +210,20 @@ func (s *FileService) fileInfoToResponse(info *FileInfo) map[string]interface{} } // GetParentFolder gets parent folder of a file with permission check -func (s *FileService) GetParentFolder(userID, fileID string) (map[string]interface{}, error) { +func (s *FileService) GetParentFolder(ctx context.Context, userID, fileID string) (map[string]interface{}, error) { // Get file - file, err := s.fileDAO.GetByID(fileID) + file, err := s.fileDAO.GetByID(ctx, dao.DB, fileID) if err != nil { return nil, err } // Permission check - if !s.checkFilePerm(s.fileDAO, file, userID) { - return nil, fmt.Errorf("No authorization.") + if !s.checkFilePerm(ctx, s.fileDAO, file, userID) { + return nil, fmt.Errorf("no authorization") } // Get parent folder - parentFolder, err := s.fileDAO.GetParentFolder(fileID) + parentFolder, err := s.fileDAO.GetParentFolder(ctx, dao.DB, fileID) if err != nil { return nil, err } @@ -227,20 +232,20 @@ func (s *FileService) GetParentFolder(userID, fileID string) (map[string]interfa } // GetAllParentFolders gets all parent folders in path with permission check -func (s *FileService) GetAllParentFolders(userID, fileID string) ([]map[string]interface{}, error) { +func (s *FileService) GetAllParentFolders(ctx context.Context, userID, fileID string) ([]map[string]interface{}, error) { // Get file - file, err := s.fileDAO.GetByID(fileID) + file, err := s.fileDAO.GetByID(ctx, dao.DB, fileID) if err != nil { return nil, err } // Permission check - if !s.checkFilePerm(s.fileDAO, file, userID) { - return nil, fmt.Errorf("No authorization.") + if !s.checkFilePerm(ctx, s.fileDAO, file, userID) { + return nil, fmt.Errorf("no authorization") } // Get all parent folders - parentFolders, err := s.fileDAO.GetAllParentFolders(fileID) + parentFolders, err := s.fileDAO.GetAllParentFolders(ctx, dao.DB, fileID) if err != nil { return nil, err } @@ -260,23 +265,27 @@ func (s *FileService) GetDocCount(ctx context.Context, tenantID string) (int64, return documentDAO.CountByTenantID(ctx, dao.DB, tenantID) } -func (s *FileService) createFolderRecursive(parentFolder *entity.File, names []string, count int, tenantID string) (*entity.File, error) { +func (s *FileService) createFolderRecursive(ctx context.Context, parentFolder *entity.File, names []string, count int, tenantID string) (*entity.File, error) { if count > len(names)-2 { return parentFolder, nil } - newFolder, err := s.fileDAO.CreateFolder(parentFolder.ID, tenantID, names[count], FileTypeFolder) + newFolder, err := s.fileDAO.CreateFolder(ctx, dao.DB, parentFolder.ID, tenantID, names[count], FileTypeFolder) if err != nil { return nil, err } - return s.createFolderRecursive(newFolder, names, count+1, tenantID) + return s.createFolderRecursive(ctx, newFolder, names, count+1, tenantID) } -func (s *FileService) getUniqueFilename(name, parentID, tenantID string) string { - existingFiles := s.fileDAO.Query(name, parentID, tenantID) +func (s *FileService) getUniqueFilename(ctx context.Context, name, parentID, tenantID string) (string, error) { + existingFiles, err := s.fileDAO.Query(ctx, dao.DB, name, parentID, tenantID) + if err != nil { + return "", err + } + if len(existingFiles) == 0 { - return name + return name, nil } base := filepath.Base(name) @@ -286,31 +295,37 @@ func (s *FileService) getUniqueFilename(name, parentID, tenantID string) string counter := 1 for { newName := fmt.Sprintf("%s_%d%s", nameWithoutExt, counter, ext) - existingFiles = s.fileDAO.Query(newName, parentID, tenantID) + existingFiles, err = s.fileDAO.Query(ctx, dao.DB, newName, parentID, tenantID) + if err != nil { + return "", err + } if len(existingFiles) == 0 { - return newName + return newName, nil } counter++ } } // CreateFolder creates a new folder or virtual file -func (s *FileService) CreateFolder(tenantID, name, parentID, fileType string) (map[string]interface{}, error) { +func (s *FileService) CreateFolder(ctx context.Context, tenantID, name, parentID, fileType string) (map[string]interface{}, error) { if parentID == "" { - rootFolder, err := s.fileDAO.GetRootFolder(tenantID) + rootFolder, err := s.fileDAO.GetRootFolder(ctx, dao.DB, tenantID) if err != nil { return nil, fmt.Errorf("failed to get root folder: %w", err) } parentID = rootFolder.ID } - if !s.fileDAO.IsParentFolderExist(parentID) { - return nil, fmt.Errorf("Parent Folder Doesn't Exist!") + if !s.fileDAO.IsParentFolderExist(ctx, dao.DB, parentID) { + return nil, fmt.Errorf("parent folder not found") } - existingFiles := s.fileDAO.Query(name, parentID, tenantID) + existingFiles, err := s.fileDAO.Query(ctx, dao.DB, name, parentID, tenantID) + if err != nil { + return nil, fmt.Errorf("failed to query existing files: %w", err) + } if len(existingFiles) > 0 { - return nil, fmt.Errorf("Duplicated folder name in the same folder.") + return nil, fmt.Errorf("duplicated folder name in the same folder") } if fileType == "" { @@ -323,7 +338,7 @@ func (s *FileService) CreateFolder(tenantID, name, parentID, fileType string) (m fileType = FileTypeVirtual } - folder, err := s.fileDAO.CreateFolder(parentID, tenantID, name, fileType) + folder, err := s.fileDAO.CreateFolder(ctx, dao.DB, parentID, tenantID, name, fileType) if err != nil { return nil, fmt.Errorf("failed to create folder: %w", err) } @@ -338,9 +353,9 @@ func (s *FileService) CreateFolder(tenantID, name, parentID, fileType string) (m // - both: move and rename simultaneously func (s *FileService) MoveFiles(ctx context.Context, uid string, srcFileIDs []string, destFileID string, newName string) (bool, string) { // 1. Get all source files - files, err := s.fileDAO.GetByIDs(srcFileIDs) + files, err := s.fileDAO.GetByIDs(ctx, dao.DB, srcFileIDs) if err != nil || len(files) == 0 { - return false, "Source files not found!" + return false, "source files not found" } // Create a map for quick lookup @@ -353,36 +368,36 @@ func (s *FileService) MoveFiles(ctx context.Context, uid string, srcFileIDs []st for _, fileID := range srcFileIDs { file, ok := filesMap[fileID] if !ok { - return false, "File or folder not found!" + return false, "file or folder not found" } if file.TenantID == "" { - return false, "Tenant not found!" + return false, "tenant not found" } // 3. Permission check - if !s.checkFilePerm(s.fileDAO, file, uid) { - return false, "No authorization." + if !s.checkFilePerm(ctx, s.fileDAO, file, uid) { + return false, "no authorization" } } // 4. Validate destination folder if provided var destFolder *entity.File if destFileID != "" { - destFolder, err = s.fileDAO.GetByID(destFileID) + destFolder, err = s.fileDAO.GetByID(ctx, dao.DB, destFileID) if err != nil || destFolder == nil { - return false, "Parent folder not found!" + return false, "parent folder not found" } // Check destination folder permission - if !s.checkFilePerm(s.fileDAO, destFolder, uid) { - return false, "No authorization to write to destination folder." + if !s.checkFilePerm(ctx, s.fileDAO, destFolder, uid) { + return false, "no authorization to write to destination folder" } if destFolder.Type != FileTypeFolder { - return false, "Destination is not a folder." + return false, "destination is not a folder" } - destAncestors, err := s.fileDAO.GetAllParentFolders(destFolder.ID) + destAncestors, err := s.fileDAO.GetAllParentFolders(ctx, dao.DB, destFolder.ID) if err != nil { - return false, "Parent folder not found!" + return false, "parent folder not found" } destAncestorIDs := make(map[string]struct{}, len(destAncestors)) @@ -396,11 +411,11 @@ func (s *FileService) MoveFiles(ctx context.Context, uid string, srcFileIDs []st } if file.ID == destFolder.ID { - return false, "Cannot move a folder to itself." + return false, "cannot move a folder to itself" } if _, ok := destAncestorIDs[file.ID]; ok { - return false, "Cannot move a folder into its own subfolder." + return false, "cannot move a folder into its own subfolder" } } } @@ -408,7 +423,7 @@ func (s *FileService) MoveFiles(ctx context.Context, uid string, srcFileIDs []st // 5. Validate new_name if provided if newName != "" { if len(srcFileIDs) > 1 { - return false, "new_name can only be used with a single file" + return false, "new name can only be used with a single file" } file := filesMap[srcFileIDs[0]] @@ -426,16 +441,24 @@ func (s *FileService) MoveFiles(ctx context.Context, uid string, srcFileIDs []st if destFolder != nil { targetParentID = destFolder.ID } - existingFiles := s.fileDAO.Query(newName, targetParentID, file.TenantID) + var existingFiles []*entity.File + existingFiles, err = s.fileDAO.Query(ctx, dao.DB, newName, targetParentID, file.TenantID) + if err != nil { + return false, fmt.Sprintf("failed to query existing files: %v", err) + } for _, f := range existingFiles { if f.Name == newName { - return false, "Duplicated file name in the same folder." + return false, "duplicated file name in the same folder" } } } else if destFolder != nil { // Plain move (no rename): check for duplicate names in destination folder for _, file := range files { - existingFiles := s.fileDAO.Query(file.Name, destFolder.ID, file.TenantID) + var existingFiles []*entity.File + existingFiles, err = s.fileDAO.Query(ctx, dao.DB, file.Name, destFolder.ID, file.TenantID) + if err != nil { + return false, fmt.Sprintf("failed to query existing files: %v", err) + } for _, f := range existingFiles { // Ignore the source file itself if f.ID != file.ID { @@ -462,7 +485,7 @@ func (s *FileService) MoveFiles(ctx context.Context, uid string, srcFileIDs []st return false, "Source files not found!" } file := filesMap[srcFileIDs[0]] - if err = s.fileDAO.UpdateByID(file.ID, map[string]interface{}{"name": newName}); err != nil { + if err = s.fileDAO.UpdateByID(ctx, dao.DB, file.ID, map[string]interface{}{"name": newName}); err != nil { return false, "Database error (File rename)!" } @@ -471,7 +494,7 @@ func (s *FileService) MoveFiles(ctx context.Context, uid string, srcFileIDs []st if err == nil && len(informs) > 0 && informs[0].DocumentID != nil { docID := *informs[0].DocumentID documentDAO := dao.NewDocumentDAO() - if err := documentDAO.UpdateByID(ctx, dao.DB, docID, map[string]interface{}{"name": newName}); err != nil { + if err = documentDAO.UpdateByID(ctx, dao.DB, docID, map[string]interface{}{"name": newName}); err != nil { return false, "Database error (Document rename)!" } } @@ -489,7 +512,10 @@ func (s *FileService) moveEntryRecursive(ctx context.Context, sourceFile *entity if sourceFile.Type == FileTypeFolder { // Handle folder move - existingFolders := s.fileDAO.Query(effectiveName, destFolder.ID, sourceFile.TenantID) + existingFolders, err := s.fileDAO.Query(ctx, dao.DB, effectiveName, destFolder.ID, sourceFile.TenantID) + if err != nil { + return fmt.Errorf("failed to query existing folders: %w", err) + } var newFolder *entity.File if len(existingFolders) > 0 { // Prevent moving a folder into itself (self-target merge) @@ -500,14 +526,14 @@ func (s *FileService) moveEntryRecursive(ctx context.Context, sourceFile *entity } else { // Create new folder var err error - newFolder, err = s.fileDAO.CreateFolder(destFolder.ID, sourceFile.TenantID, effectiveName, FileTypeFolder) + newFolder, err = s.fileDAO.CreateFolder(ctx, dao.DB, destFolder.ID, sourceFile.TenantID, effectiveName, FileTypeFolder) if err != nil { return fmt.Errorf("failed to create destination folder: %w", err) } } // Recursively move sub-files - subFiles, err := s.fileDAO.ListAllFilesByParentID(sourceFile.ID) + subFiles, err := s.fileDAO.ListAllFilesByParentID(ctx, dao.DB, sourceFile.ID) if err != nil { return err } @@ -518,7 +544,7 @@ func (s *FileService) moveEntryRecursive(ctx context.Context, sourceFile *entity } // Delete the source folder - return s.fileDAO.Delete(sourceFile.ID) + return s.fileDAO.Delete(ctx, dao.DB, sourceFile.ID) } // Handle non-folder file move @@ -556,7 +582,7 @@ func (s *FileService) moveEntryRecursive(ctx context.Context, sourceFile *entity } if len(updates) > 0 { - if err := s.fileDAO.UpdateByID(sourceFile.ID, updates); err != nil { + if err := s.fileDAO.UpdateByID(ctx, dao.DB, sourceFile.ID, updates); err != nil { return fmt.Errorf("database error (File update): %w", err) } } diff --git a/internal/service/file/file_test.go b/internal/service/file/file_test.go index 29cc90a83f..c30aa63541 100644 --- a/internal/service/file/file_test.go +++ b/internal/service/file/file_test.go @@ -2,6 +2,7 @@ package file import ( "bytes" + "context" "errors" "io" @@ -33,7 +34,7 @@ func sptr(s string) *string { return &s } // testFilePerm controls the permission check returned by testFileService. // Tests that need to simulate denied access can set it to a function that // returns false. -var testFilePerm CheckFilePermFunc = func(_ *dao.FileDAO, _ *entity.File, _ string) bool { return true } +var testFilePerm CheckFilePermFunc = func(_ context.Context, _ *dao.FileDAO, _ *entity.File, _ string) bool { return true } func testFileService() *FileService { return &FileService{ diff --git a/internal/service/file/file_upload.go b/internal/service/file/file_upload.go index f7dff13e85..7d2a34998b 100644 --- a/internal/service/file/file_upload.go +++ b/internal/service/file/file_upload.go @@ -7,6 +7,7 @@ import ( "mime/multipart" "net/http" "ragflow/internal/common" + "ragflow/internal/dao" "ragflow/internal/entity" "ragflow/internal/storage" "ragflow/internal/utility" @@ -17,14 +18,14 @@ import ( // UploadFile uploads files to a folder func (s *FileService) UploadFile(ctx context.Context, tenantID, parentID string, files []*multipart.FileHeader) ([]map[string]interface{}, error) { if parentID == "" { - rootFolder, err := s.fileDAO.GetRootFolder(tenantID) + rootFolder, err := s.fileDAO.GetRootFolder(ctx, dao.DB, tenantID) if err != nil { return nil, fmt.Errorf("failed to get root folder: %w", err) } parentID = rootFolder.ID } - _, err := s.fileDAO.GetByID(parentID) + _, err := s.fileDAO.GetByID(ctx, dao.DB, parentID) if err != nil { return nil, fmt.Errorf("Can't find this folder!") } @@ -54,7 +55,7 @@ func (s *FileService) UploadFile(ctx context.Context, tenantID, parentID string, for _, fileHeader := range files { filename := fileHeader.Filename if filename == "" { - return nil, fmt.Errorf("No file selected!") + return nil, fmt.Errorf("no file selected") } fileType := utility.FilenameType(filename) @@ -62,7 +63,7 @@ func (s *FileService) UploadFile(ctx context.Context, tenantID, parentID string, fileObjNames := s.parseFilePath(filename) var idList []string - idList, err = s.fileDAO.GetIDListByID(parentID, fileObjNames, 1, []string{parentID}) + idList, err = s.fileDAO.GetIDListByID(ctx, dao.DB, parentID, fileObjNames, 1, []string{parentID}) if err != nil { return nil, fmt.Errorf("failed to get file ID list: %w", err) } @@ -70,21 +71,21 @@ func (s *FileService) UploadFile(ctx context.Context, tenantID, parentID string, var lastFolder *entity.File if len(fileObjNames) != len(idList)-1 { lastID := idList[len(idList)-1] - lastFolder, err = s.fileDAO.GetByID(lastID) + lastFolder, err = s.fileDAO.GetByID(ctx, dao.DB, lastID) if err != nil { - return nil, fmt.Errorf("Folder not found!") + return nil, fmt.Errorf("folder not found") } var createdFolder *entity.File - createdFolder, err = s.createFolderRecursive(lastFolder, fileObjNames, len(idList), tenantID) + createdFolder, err = s.createFolderRecursive(ctx, lastFolder, fileObjNames, len(idList), tenantID) if err != nil { return nil, fmt.Errorf("failed to create folder: %w", err) } lastFolder = createdFolder } else { lastID := idList[len(idList)-2] - lastFolder, err = s.fileDAO.GetByID(lastID) + lastFolder, err = s.fileDAO.GetByID(ctx, dao.DB, lastID) if err != nil { - return nil, fmt.Errorf("Folder not found!") + return nil, fmt.Errorf("folder not found") } } @@ -93,13 +94,15 @@ func (s *FileService) UploadFile(ctx context.Context, tenantID, parentID string, location += "_" } - src, err := fileHeader.Open() + var src multipart.File + src, err = fileHeader.Open() if err != nil { return nil, fmt.Errorf("failed to open uploaded file: %w", err) } - defer src.Close() - data, err := io.ReadAll(src) + var data []byte + data, err = io.ReadAll(src) + src.Close() if err != nil { return nil, fmt.Errorf("failed to read file data: %w", err) } @@ -108,7 +111,11 @@ func (s *FileService) UploadFile(ctx context.Context, tenantID, parentID string, return nil, fmt.Errorf("failed to store file: %w", err) } - uniqueName := s.getUniqueFilename(fileObjNames[len(fileObjNames)-1], lastFolder.ID, tenantID) + var uniqueName string + uniqueName, err = s.getUniqueFilename(ctx, fileObjNames[len(fileObjNames)-1], lastFolder.ID, tenantID) + if err != nil { + return nil, fmt.Errorf("failed to get unique filename: %w", err) + } fileRecord := &entity.File{ ID: utility.GenerateToken(), @@ -122,7 +129,7 @@ func (s *FileService) UploadFile(ctx context.Context, tenantID, parentID string, SourceType: "", } - if err = s.fileDAO.Insert(fileRecord); err != nil { + if err = s.fileDAO.Insert(ctx, dao.DB, fileRecord); err != nil { return nil, fmt.Errorf("failed to insert file record: %w", err) } diff --git a/internal/service/file_permission.go b/internal/service/file_permission.go index a0b5d17f08..a91bf12887 100644 --- a/internal/service/file_permission.go +++ b/internal/service/file_permission.go @@ -1,6 +1,7 @@ package service import ( + "context" "ragflow/internal/dao" "ragflow/internal/entity" ) @@ -10,12 +11,12 @@ import ( // dataset the file is linked to. It mirrors Python's check_file_team_permission // and is shared by FileService and File2DocumentService, which previously each // carried an identical copy of this logic. -func CheckFileTeamPermission(fileDAO *dao.FileDAO, file *entity.File, userID string) bool { +func CheckFileTeamPermission(ctx context.Context, fileDAO *dao.FileDAO, file *entity.File, userID string) bool { if file.TenantID == userID { return true } - datasetIDs, err := fileDAO.GetDatasetIDByFileID(file.ID) + datasetIDs, err := fileDAO.GetDatasetIDByFileID(ctx, dao.DB, file.ID) if err != nil || len(datasetIDs) == 0 { return false } diff --git a/internal/service/oauth_login.go b/internal/service/oauth_login.go index 84035e1dce..8077a867dd 100644 --- a/internal/service/oauth_login.go +++ b/internal/service/oauth_login.go @@ -185,7 +185,7 @@ func (s *UserService) OAuthCallback(ctx context.Context, channel, code, callback } // New user — register a fresh account bound to this channel. - created, ecode, cerr := s.registerOAuthUser(channel, info) + created, ecode, cerr := s.registerOAuthUser(ctx, channel, info) if cerr != nil { return nil, ecode, cerr } @@ -195,7 +195,7 @@ func (s *UserService) OAuthCallback(ctx context.Context, channel, code, callback // registerOAuthUser provisions a new user + tenant for an OAuth identity. // Models the relevant fields the email-password Register path sets so the // rest of the app (kbs, files, llm config) sees a fully-shaped tenant. -func (s *UserService) registerOAuthUser(channel string, info *oauth.UserInfo) (*entity.User, common.ErrorCode, error) { +func (s *UserService) registerOAuthUser(ctx context.Context, channel string, info *oauth.UserInfo) (*entity.User, common.ErrorCode, error) { cfg := server.GetConfig() userID := utility.GenerateToken() accessToken := utility.GenerateToken() @@ -272,7 +272,7 @@ func (s *UserService) registerOAuthUser(channel string, info *oauth.UserInfo) (* _ = tenantDAO.Delete(userID) return nil, common.CodeServerError, fmt.Errorf("Failed to register %s: %w", info.Email, err) } - if err := fileDAO.Create(rootFile); err != nil { + if err := fileDAO.Create(ctx, dao.DB, rootFile); err != nil { _ = s.userDAO.DeleteByID(userID) _ = tenantDAO.Delete(userID) _ = userTenantDAO.Delete(userTenantID) diff --git a/internal/service/skill_indexer.go b/internal/service/skill_indexer.go index cf51c17687..68bb2fa1cf 100644 --- a/internal/service/skill_indexer.go +++ b/internal/service/skill_indexer.go @@ -461,7 +461,7 @@ func (s *SkillIndexerService) ReindexAll(ctx context.Context, tenantID, spaceID // Find the actual space folder ID by space name (consistent with frontend behavior) // Frontend uses space name to find folder, not space.FolderID which may be outdated - spaceFolderID, err := s.getSpaceFolderIDByName(tenantID, space.Name) + spaceFolderID, err := s.getSpaceFolderIDByName(ctx, tenantID, space.Name) if err != nil { return nil, fmt.Errorf("failed to find space folder: %w", err) } @@ -504,7 +504,7 @@ func (s *SkillIndexerService) getSkillsFromFileSystem(ctx context.Context, tenan var skills []SkillInfo // Get all skill folders under the space - skillFolders, err := s.fileDAO.ListByParentID(spaceFolderID) + skillFolders, err := s.fileDAO.ListByParentID(ctx, dao.DB, spaceFolderID) if err != nil { return nil, fmt.Errorf("failed to list skill folders: %w", err) } @@ -517,7 +517,7 @@ func (s *SkillIndexerService) getSkillsFromFileSystem(ctx context.Context, tenan } // Get all versions of this skill - versions, err := s.fileDAO.ListByParentID(skillFolder.ID) + versions, err := s.fileDAO.ListByParentID(ctx, dao.DB, skillFolder.ID) if err != nil { common.Warn(fmt.Sprintf("failed to list versions for skill %s: %v", skillFolder.Name, err)) continue @@ -590,7 +590,7 @@ func (s *SkillIndexerService) findLatestVersion(versions []*entity.File) *entity // getSkillContentFromFolder reads skill content from the version folder func (s *SkillIndexerService) getSkillContentFromFolder(ctx context.Context, tenantID string, skillFolder, versionFolder *entity.File, spaceID string) (*SkillInfo, error) { // Get all files in the version folder - files, err := s.fileDAO.ListByParentID(versionFolder.ID) + files, err := s.fileDAO.ListByParentID(ctx, dao.DB, versionFolder.ID) if err != nil { return nil, fmt.Errorf("failed to list files in version folder: %w", err) } @@ -672,15 +672,15 @@ func isTextFileForSkill(fileName string) bool { // getSpaceFolderIDByName finds the space folder ID by space name (consistent with frontend behavior) // Frontend finds space folder by listing folders under skills folder and matching by name -func (s *SkillIndexerService) getSpaceFolderIDByName(tenantID, spaceName string) (string, error) { +func (s *SkillIndexerService) getSpaceFolderIDByName(ctx context.Context, tenantID, spaceName string) (string, error) { // Get root folder - rootFolder, err := s.fileDAO.GetRootFolder(tenantID) + rootFolder, err := s.fileDAO.GetRootFolder(ctx, dao.DB, tenantID) if err != nil { return "", fmt.Errorf("failed to get root folder: %w", err) } // Find skills folder under root - files, _, err := s.fileDAO.GetByPfID(tenantID, rootFolder.ID, 0, 0, "name", false, "") + files, _, err := s.fileDAO.GetByPfID(ctx, dao.DB, tenantID, rootFolder.ID, 0, 0, "name", false, "") if err != nil { return "", fmt.Errorf("failed to list root folder contents: %w", err) } @@ -698,7 +698,7 @@ func (s *SkillIndexerService) getSpaceFolderIDByName(tenantID, spaceName string) } // Find space folder by name under skills folder - spaceFolders, _, err := s.fileDAO.GetByPfID(tenantID, skillsFolderID, 0, 0, "name", false, "") + spaceFolders, _, err := s.fileDAO.GetByPfID(ctx, dao.DB, tenantID, skillsFolderID, 0, 0, "name", false, "") if err != nil { return "", fmt.Errorf("failed to list skills folder contents: %w", err) } diff --git a/internal/service/skill_space.go b/internal/service/skill_space.go index 2b73c7a0b3..09bbfaaa08 100644 --- a/internal/service/skill_space.go +++ b/internal/service/skill_space.go @@ -77,7 +77,7 @@ type UpdateSpaceRequest struct { // getSkillsFolderID gets or creates the skills folder for a tenant // Uses tenant-scoped locking to prevent duplicate folder creation -func (s *SkillSpaceService) getSkillsFolderID(tenantID string) (string, error) { +func (s *SkillSpaceService) getSkillsFolderID(ctx context.Context, tenantID string) (string, error) { // Return cached value if available (read lock) s.skillsFolderMu.RLock() if cachedID, ok := s.skillsFolderCache[tenantID]; ok && cachedID != "" { @@ -100,13 +100,13 @@ func (s *SkillSpaceService) getSkillsFolderID(tenantID string) (string, error) { s.skillsFolderMu.RUnlock() // Get root folder - rootFolder, err := s.fileDAO.GetRootFolder(tenantID) + rootFolder, err := s.fileDAO.GetRootFolder(ctx, dao.DB, tenantID) if err != nil { return "", fmt.Errorf("failed to get root folder: %w", err) } // Look for skills folder under root - files, _, err := s.fileDAO.GetByPfID(tenantID, rootFolder.ID, 0, 0, "name", false, "") + files, _, err := s.fileDAO.GetByPfID(ctx, dao.DB, tenantID, rootFolder.ID, 0, 0, "name", false, "") if err != nil { return "", fmt.Errorf("failed to list root folder contents: %w", err) } @@ -135,7 +135,7 @@ func (s *SkillSpaceService) getSkillsFolderID(tenantID string) (string, error) { SourceType: "system", } - if err := s.fileDAO.Create(folder); err != nil { + if err = s.fileDAO.Create(ctx, dao.DB, folder); err != nil { return "", fmt.Errorf("failed to create skills folder: %w", err) } @@ -148,7 +148,7 @@ func (s *SkillSpaceService) getSkillsFolderID(tenantID string) (string, error) { } // CreateSpace creates a new skills space with associated folder -func (s *SkillSpaceService) CreateSpace(req *CreateSpaceRequest) (map[string]interface{}, common.ErrorCode, error) { +func (s *SkillSpaceService) CreateSpace(ctx context.Context, req *CreateSpaceRequest) (map[string]interface{}, common.ErrorCode, error) { // Validate name if req.Name == "" { return nil, common.CodeDataError, fmt.Errorf("space name is required") @@ -181,12 +181,12 @@ func (s *SkillSpaceService) CreateSpace(req *CreateSpaceRequest) (map[string]int // Check if there's a deleted/non-active space with the same name and permanently delete it // This handles the case where a previous creation failed partially // Only delete non-active spaces (status != '1') to prevent TOCTOU race - if err := s.spaceDAO.DeletePermanentByName(req.TenantID, req.Name); err != nil { + if err = s.spaceDAO.DeletePermanentByName(req.TenantID, req.Name); err != nil { common.Warn("Failed to delete permanent space by name", zap.Error(err)) } // Get skills folder ID - skillsFolderID, err := s.getSkillsFolderID(req.TenantID) + skillsFolderID, err := s.getSkillsFolderID(ctx, req.TenantID) if err != nil { common.Error("Failed to get skills folder ID", err) return nil, common.CodeOperatingError, err @@ -194,11 +194,14 @@ func (s *SkillSpaceService) CreateSpace(req *CreateSpaceRequest) (map[string]int // Check if there's an existing folder with the same name under skills folder // If exists, delete it to prevent duplicate folder names - existingFolders := s.fileDAO.Query(req.Name, skillsFolderID, req.TenantID) + existingFolders, err := s.fileDAO.Query(ctx, dao.DB, req.Name, skillsFolderID, req.TenantID) + if err != nil { + return nil, common.CodeOperatingError, fmt.Errorf("failed to query existing folders: %w", err) + } for _, f := range existingFolders { if f.Type == "folder" && f.Name == req.Name { common.Info("Deleting existing space folder with same name", zap.String("folderID", f.ID), zap.String("name", req.Name)) - if err := s.deleteFolderRecursive(f.ID); err != nil { + if err = s.deleteFolderRecursive(ctx, f.ID); err != nil { common.Warn("Failed to delete existing folder", zap.String("folderID", f.ID), zap.Error(err)) } break @@ -221,7 +224,7 @@ func (s *SkillSpaceService) CreateSpace(req *CreateSpaceRequest) (map[string]int SourceType: "skill_space", } - if err := s.fileDAO.Create(folder); err != nil { + if err = s.fileDAO.Create(ctx, dao.DB, folder); err != nil { common.Error("Failed to create space folder", err) return nil, common.CodeOperatingError, fmt.Errorf("failed to create space folder: %w", err) } @@ -239,10 +242,10 @@ func (s *SkillSpaceService) CreateSpace(req *CreateSpaceRequest) (map[string]int Status: "1", } - if err := s.spaceDAO.Create(space); err != nil { + if err = s.spaceDAO.Create(space); err != nil { // Rollback: delete the created folder common.Error("Failed to create space in database", err) - s.fileDAO.DeleteByIDs([]string{folderID}) + s.fileDAO.DeleteByIDs(ctx, dao.DB, []string{folderID}) return nil, common.CodeOperatingError, fmt.Errorf("failed to create space: %w", err) } @@ -310,7 +313,7 @@ func (s *SkillSpaceService) GetSpace(spaceID, tenantID string) (map[string]inter } // UpdateSpace updates a skills space -func (s *SkillSpaceService) UpdateSpace(spaceID string, tenantID string, req *UpdateSpaceRequest) (map[string]interface{}, common.ErrorCode, error) { +func (s *SkillSpaceService) UpdateSpace(ctx context.Context, spaceID string, tenantID string, req *UpdateSpaceRequest) (map[string]interface{}, common.ErrorCode, error) { space, err := s.spaceDAO.GetByID(spaceID) if err != nil { return nil, common.CodeDataError, fmt.Errorf("space not found") @@ -335,12 +338,12 @@ func (s *SkillSpaceService) UpdateSpace(spaceID string, tenantID string, req *Up updates["name"] = req.Name // Update space first, then folder (atomic-like behavior with rollback on failure) - if err := s.spaceDAO.UpdateByID(spaceID, updates); err != nil { + if err = s.spaceDAO.UpdateByID(spaceID, updates); err != nil { return nil, common.CodeOperatingError, fmt.Errorf("failed to update space name: %w", err) } // Update folder name as well - if this fails, rollback space name - if err := s.fileDAO.UpdateByID(space.FolderID, map[string]interface{}{"name": req.Name}); err != nil { + if err = s.fileDAO.UpdateByID(ctx, dao.DB, space.FolderID, map[string]interface{}{"name": req.Name}); err != nil { common.Error("Failed to update folder name, rolling back space name", err) // Rollback space name if rollbackErr := s.spaceDAO.UpdateByID(spaceID, map[string]interface{}{"name": originalName}); rollbackErr != nil { @@ -489,9 +492,9 @@ func (s *SkillSpaceService) asyncDeleteSpace(spaceID, folderID, tenantID string, } // deleteFolderRecursive recursively deletes a folder and all its contents -func (s *SkillSpaceService) deleteFolderRecursive(folderID string) error { +func (s *SkillSpaceService) deleteFolderRecursive(ctx context.Context, folderID string) error { // Get all children - children, err := s.fileDAO.ListByParentID(folderID) + children, err := s.fileDAO.ListByParentID(ctx, dao.DB, folderID) if err != nil { common.Error(fmt.Sprintf("Failed to list children for folder %s", folderID), err) return err @@ -504,7 +507,7 @@ func (s *SkillSpaceService) deleteFolderRecursive(folderID string) error { for _, child := range children { if child.Type == "folder" { common.Debug("Recursively deleting child folder", zap.String("folder_id", child.ID), zap.String("folder_name", child.Name)) - if err := s.deleteFolderRecursive(child.ID); err != nil { + if err = s.deleteFolderRecursive(ctx, child.ID); err != nil { common.Warn("Failed to delete child folder", zap.String("folder_id", child.ID), zap.Error(err)) } } else { @@ -517,7 +520,7 @@ func (s *SkillSpaceService) deleteFolderRecursive(folderID string) error { // Delete all non-folder files in batch if len(fileIDs) > 0 { common.Info("Deleting files in folder", zap.String("folder_id", folderID), zap.Int("file_count", len(fileIDs))) - if _, err := s.fileDAO.DeleteByIDs(fileIDs); err != nil { + if _, err = s.fileDAO.DeleteByIDs(ctx, dao.DB, fileIDs); err != nil { common.Warn("Failed to delete files in folder", zap.String("folder_id", folderID), zap.Strings("file_ids", fileIDs), zap.Error(err)) // Continue to delete folder even if file deletion fails } @@ -525,7 +528,7 @@ func (s *SkillSpaceService) deleteFolderRecursive(folderID string) error { // Delete the folder itself common.Info("Deleting folder", zap.String("folder_id", folderID)) - _, err = s.fileDAO.DeleteByIDs([]string{folderID}) + _, err = s.fileDAO.DeleteByIDs(ctx, dao.DB, []string{folderID}) if err != nil { common.Error(fmt.Sprintf("Failed to delete folder %s", folderID), err) }