Go: add context (#17354)

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
Jin Hai
2026-07-24 20:19:41 +08:00
committed by GitHub
parent 64dbc518aa
commit d9e359d481
25 changed files with 438 additions and 351 deletions

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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,
)

View File

@@ -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 -> <dataset name>. 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

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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{

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)
}