diff --git a/api/apps/services/file_api_service.py b/api/apps/services/file_api_service.py index 29cb6a2a7a..f3060741af 100644 --- a/api/apps/services/file_api_service.py +++ b/api/apps/services/file_api_service.py @@ -164,11 +164,12 @@ def list_files(tenant_id: str, args: dict): FileService.init_knowledgebase_docs(pf_id, tenant_id) FileService.init_skills_folder(pf_id, tenant_id) - e, _ = FileService.get_by_id(pf_id) + e, folder = FileService.get_by_id(pf_id) if not e: return False, "Folder not found!" - files, total = FileService.get_by_pf_id(tenant_id, pf_id, page_number, items_per_page, orderby, desc, keywords) + exclude_skills = folder.id == folder.parent_id + files, total = FileService.get_by_pf_id(tenant_id, pf_id, page_number, items_per_page, orderby, desc, keywords, exclude_skills) parent_folder = FileService.get_parent_folder(pf_id) if not parent_folder: diff --git a/api/db/services/file_service.py b/api/db/services/file_service.py index e92b505131..a9e93eac24 100644 --- a/api/db/services/file_service.py +++ b/api/db/services/file_service.py @@ -51,7 +51,7 @@ class FileService(CommonService): @classmethod @DB.connection_context() - def get_by_pf_id(cls, tenant_id, pf_id, page_number, items_per_page, orderby, desc, keywords): + def get_by_pf_id(cls, tenant_id, pf_id, page_number, items_per_page, orderby, desc, keywords, exclude_skills): # Get files by parent folder ID with pagination and filtering # Args: # tenant_id: ID of the tenant @@ -61,6 +61,7 @@ class FileService(CommonService): # orderby: Field to order by # desc: Boolean indicating descending order # keywords: Search keywords + # exclude_skills: Whether to exclude the skills folder directly under pf_id # Returns: # Tuple of (file_list, total_count) if keywords: @@ -72,6 +73,8 @@ class FileService(CommonService): ) else: files = cls.model.select().where((cls.model.tenant_id == tenant_id), (cls.model.parent_id == pf_id), ~(cls.model.id == pf_id)) + if exclude_skills: + files = files.where(~((cls.model.parent_id == pf_id) & (cls.model.name == SKILLS_FOLDER_NAME))) count = files.count() if desc: files = files.order_by(cls.model.getter_by(orderby).desc()) diff --git a/internal/dao/file.go b/internal/dao/file.go index 7f92ec61e1..281d6c032c 100644 --- a/internal/dao/file.go +++ b/internal/dao/file.go @@ -49,7 +49,7 @@ func (dao *FileDAO) GetByID(ctx context.Context, db *gorm.DB, id string) (*entit // When keywords is empty, only direct children of pfID are listed; when // keywords is non-empty, the search covers the whole subtree under pfID so // files and folders nested in sub-folders can be found too. -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) { +func (dao *FileDAO) GetByPfID(ctx context.Context, db *gorm.DB, tenantID, pfID string, page, pageSize int, orderBy string, desc bool, keywords string, excludeSkills bool) ([]*entity.File, int64, error) { var files []*entity.File var total int64 @@ -66,6 +66,9 @@ func (dao *FileDAO) GetByPfID(ctx context.Context, db *gorm.DB, tenantID, pfID s } else { query = query.Where("parent_id = ?", pfID) } + if excludeSkills { + query = query.Where("NOT (parent_id = ? AND name = ?)", pfID, SkillsFolderName) + } // Count total if err := query.Count(&total).Error; err != nil { @@ -436,6 +439,9 @@ func reparentAndDeleteFolder(ctx context.Context, db *gorm.DB, dupID, keepID str // DatasetFolderName is the folder name for dataset const DatasetFolderName = ".knowledgebase" +// SkillsFolderName is the folder name for skills +const SkillsFolderName = "skills" + // InitDatasetDocs initializes dataset documents for tenant. // This matches Python's FileService.init_dataset_docs method. // Deduplicates duplicate entries that may have been created by diff --git a/internal/dao/file_test.go b/internal/dao/file_test.go index 6a9a436cd0..82da00431a 100644 --- a/internal/dao/file_test.go +++ b/internal/dao/file_test.go @@ -76,7 +76,7 @@ func TestFileDAO_GetByPfID_KeywordsSearchesSubtree(t *testing.T) { d := NewFileDAO() ctx := t.Context() - files, total, err := d.GetByPfID(ctx, db, "t1", "root", 1, 15, "create_time", true, "report") + files, total, err := d.GetByPfID(ctx, db, "t1", "root", 1, 15, "create_time", true, "report", false) if err != nil { t.Fatalf("GetByPfID failed: %v", err) } @@ -85,7 +85,7 @@ func TestFileDAO_GetByPfID_KeywordsSearchesSubtree(t *testing.T) { } // Nested file two levels down must be found from the root folder. - files, total, err = d.GetByPfID(ctx, db, "t1", "root", 1, 15, "create_time", true, "notes") + files, total, err = d.GetByPfID(ctx, db, "t1", "root", 1, 15, "create_time", true, "notes", false) if err != nil { t.Fatalf("GetByPfID failed: %v", err) } @@ -94,7 +94,7 @@ func TestFileDAO_GetByPfID_KeywordsSearchesSubtree(t *testing.T) { } // Folders themselves are searchable by name. - files, total, err = d.GetByPfID(ctx, db, "t1", "root", 1, 15, "create_time", true, "sub") + files, total, err = d.GetByPfID(ctx, db, "t1", "root", 1, 15, "create_time", true, "sub", false) if err != nil { t.Fatalf("GetByPfID failed: %v", err) } @@ -110,7 +110,7 @@ func TestFileDAO_GetByPfID_KeywordsScopedToSubtree(t *testing.T) { ctx := t.Context() // Searching inside dirA must not match files outside that subtree. - files, total, err := d.GetByPfID(ctx, db, "t1", "dirA", 1, 15, "create_time", true, "report") + files, total, err := d.GetByPfID(ctx, db, "t1", "dirA", 1, 15, "create_time", true, "report", false) if err != nil { t.Fatalf("GetByPfID failed: %v", err) } @@ -119,7 +119,7 @@ func TestFileDAO_GetByPfID_KeywordsScopedToSubtree(t *testing.T) { } // Tenant isolation still applies. - files, total, err = d.GetByPfID(ctx, db, "t2", "root", 1, 15, "create_time", true, "report") + files, total, err = d.GetByPfID(ctx, db, "t2", "root", 1, 15, "create_time", true, "report", false) if err != nil { t.Fatalf("GetByPfID failed: %v", err) } @@ -134,7 +134,7 @@ func TestFileDAO_GetByPfID_NoKeywordsListsDirectChildren(t *testing.T) { d := NewFileDAO() ctx := t.Context() - files, total, err := d.GetByPfID(ctx, db, "t1", "root", 1, 15, "create_time", true, "") + files, total, err := d.GetByPfID(ctx, db, "t1", "root", 1, 15, "create_time", true, "", false) if err != nil { t.Fatalf("GetByPfID failed: %v", err) } diff --git a/internal/service/file/file_folder.go b/internal/service/file/file_folder.go index 79e6a58973..927530e8ba 100644 --- a/internal/service/file/file_folder.go +++ b/internal/service/file/file_folder.go @@ -45,12 +45,14 @@ func (s *FileService) ListFiles(ctx context.Context, tenantID, pfID string, page } // Check if parent folder exists - if _, err := s.fileDAO.GetByID(ctx, dao.DB, pfID); err != nil { + folder, err := s.fileDAO.GetByID(ctx, dao.DB, pfID) + if err != nil { return nil, fmt.Errorf("folder not found") } // Get files by parent folder ID - files, total, err := s.fileDAO.GetByPfID(ctx, dao.DB, tenantID, pfID, page, pageSize, orderby, desc, keywords) + excludeSkills := folder.ID == folder.ParentID + files, total, err := s.fileDAO.GetByPfID(ctx, dao.DB, tenantID, pfID, page, pageSize, orderby, desc, keywords, excludeSkills) if err != nil { return nil, err } diff --git a/internal/service/skill_indexer.go b/internal/service/skill_indexer.go index 42eb02baa5..c7ce6d3620 100644 --- a/internal/service/skill_indexer.go +++ b/internal/service/skill_indexer.go @@ -681,7 +681,7 @@ func (s *SkillIndexerService) getSpaceFolderIDByName(ctx context.Context, tenant } // Find skills folder under root - files, _, err := s.fileDAO.GetByPfID(ctx, dao.DB, tenantID, rootFolder.ID, 0, 0, "name", false, "") + files, _, err := s.fileDAO.GetByPfID(ctx, dao.DB, tenantID, rootFolder.ID, 0, 0, "name", false, "", false) if err != nil { return "", fmt.Errorf("failed to list root folder contents: %w", err) } @@ -699,7 +699,7 @@ func (s *SkillIndexerService) getSpaceFolderIDByName(ctx context.Context, tenant } // Find space folder by name under skills folder - spaceFolders, _, err := s.fileDAO.GetByPfID(ctx, dao.DB, tenantID, skillsFolderID, 0, 0, "name", false, "") + spaceFolders, _, err := s.fileDAO.GetByPfID(ctx, dao.DB, tenantID, skillsFolderID, 0, 0, "name", false, "", false) if err != nil { return "", fmt.Errorf("failed to list skills folder contents: %w", err) } diff --git a/internal/service/skill_space.go b/internal/service/skill_space.go index 72ea4c04d2..0405cbc6f5 100644 --- a/internal/service/skill_space.go +++ b/internal/service/skill_space.go @@ -106,7 +106,7 @@ func (s *SkillSpaceService) getSkillsFolderID(ctx context.Context, tenantID stri } // Look for skills folder under root - files, _, err := s.fileDAO.GetByPfID(ctx, dao.DB, tenantID, rootFolder.ID, 0, 0, "name", false, "") + files, _, err := s.fileDAO.GetByPfID(ctx, dao.DB, tenantID, rootFolder.ID, 0, 0, "name", false, "", false) if err != nil { return "", fmt.Errorf("failed to list root folder contents: %w", err) }