2026-03-04 19:17:16 +08:00
//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package dao
import (
2026-07-24 20:19:41 +08:00
"context"
2026-07-03 12:06:45 +08:00
"fmt"
"log"
2026-03-27 19:25:18 +08:00
"ragflow/internal/entity"
2026-07-07 17:22:08 +08:00
"ragflow/internal/utility"
2026-03-04 19:17:16 +08:00
"strings"
2026-07-24 20:19:41 +08:00
"gorm.io/gorm"
2026-03-04 19:17:16 +08:00
)
// FileDAO file data access object
type FileDAO struct { }
// NewFileDAO create file DAO
func NewFileDAO ( ) * FileDAO {
return & FileDAO { }
}
// GetByID gets file by ID
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) GetByID ( ctx context . Context , db * gorm . DB , id string ) ( * entity . File , error ) {
2026-03-27 19:25:18 +08:00
var file entity . File
2026-07-24 20:19:41 +08:00
err := db . WithContext ( ctx ) . Where ( "id = ?" , id ) . First ( & file ) . Error
2026-03-04 19:17:16 +08:00
if err != nil {
return nil , err
}
return & file , nil
}
// GetByPfID gets files by parent folder ID with pagination and filtering
2026-07-24 20:19:41 +08:00
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 ) {
2026-03-27 19:25:18 +08:00
var files [ ] * entity . File
2026-03-04 19:17:16 +08:00
var total int64
2026-07-24 20:19:41 +08:00
query := db . WithContext ( ctx ) . Model ( & entity . File { } ) .
2026-03-04 19:17:16 +08:00
Where ( "tenant_id = ? AND parent_id = ? AND id != ?" , tenantID , pfID , pfID )
// Apply keyword filter
if keywords != "" {
query = query . Where ( "LOWER(name) LIKE ?" , "%" + strings . ToLower ( keywords ) + "%" )
}
// Count total
if err := query . Count ( & total ) . Error ; err != nil {
return nil , 0 , err
}
// Apply ordering
orderDirection := "ASC"
if desc {
orderDirection = "DESC"
}
2026-07-24 20:19:41 +08:00
query = query . Order ( orderBy + " " + orderDirection )
2026-03-04 19:17:16 +08:00
// Apply pagination
if page > 0 && pageSize > 0 {
offset := ( page - 1 ) * pageSize
if err := query . Offset ( offset ) . Limit ( pageSize ) . Find ( & files ) . Error ; err != nil {
return nil , 0 , err
}
} else {
if err := query . Find ( & files ) . Error ; err != nil {
return nil , 0 , err
}
}
return files , total , nil
}
// GetRootFolder gets or creates root folder for tenant
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) GetRootFolder ( ctx context . Context , db * gorm . DB , tenantID string ) ( * entity . File , error ) {
2026-03-27 19:25:18 +08:00
var file entity . File
2026-07-24 20:19:41 +08:00
err := db . WithContext ( ctx ) . Where ( "tenant_id = ? AND parent_id = id" , tenantID ) . First ( & file ) . Error
2026-03-04 19:17:16 +08:00
if err == nil {
return & file , nil
}
// Create root folder if not exists
2026-07-07 17:22:08 +08:00
fileID := utility . GenerateToken ( )
2026-03-27 19:25:18 +08:00
file = entity . File {
2026-03-04 19:17:16 +08:00
ID : fileID ,
ParentID : fileID ,
TenantID : tenantID ,
CreatedBy : tenantID ,
Name : "/" ,
Type : "folder" ,
Size : 0 ,
}
file . SourceType = ""
2026-07-24 20:19:41 +08:00
if err = db . WithContext ( ctx ) . Create ( & file ) . Error ; err != nil {
2026-03-04 19:17:16 +08:00
return nil , err
}
return & file , nil
}
// GetParentFolder gets parent folder of a file
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) GetParentFolder ( ctx context . Context , db * gorm . DB , fileID string ) ( * entity . File , error ) {
2026-03-27 19:25:18 +08:00
var file entity . File
2026-07-24 20:19:41 +08:00
err := db . WithContext ( ctx ) . Where ( "id = ?" , fileID ) . First ( & file ) . Error
2026-03-04 19:17:16 +08:00
if err != nil {
return nil , err
}
2026-03-27 19:25:18 +08:00
var parentFile entity . File
2026-07-24 20:19:41 +08:00
err = db . WithContext ( ctx ) . Where ( "id = ?" , file . ParentID ) . First ( & parentFile ) . Error
2026-03-04 19:17:16 +08:00
if err != nil {
return nil , err
}
return & parentFile , nil
}
// ListByParentID lists all files by parent ID (including subfolders)
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) ListByParentID ( ctx context . Context , db * gorm . DB , parentID string ) ( [ ] * entity . File , error ) {
2026-03-27 19:25:18 +08:00
var files [ ] * entity . File
2026-07-24 20:19:41 +08:00
err := db . WithContext ( ctx ) . Where ( "parent_id = ? AND id != ?" , parentID , parentID ) . Find ( & files ) . Error
2026-03-04 19:17:16 +08:00
return files , err
}
// GetFolderSize calculates folder size recursively
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) GetFolderSize ( ctx context . Context , db * gorm . DB , folderID string ) ( int64 , error ) {
2026-03-04 19:17:16 +08:00
var size int64
var dfs func ( parentID string ) error
dfs = func ( parentID string ) error {
2026-03-27 19:25:18 +08:00
var files [ ] * entity . File
2026-07-24 20:19:41 +08:00
if err := db . WithContext ( ctx ) . Select ( "id" , "size" , "type" ) .
2026-03-04 19:17:16 +08:00
Where ( "parent_id = ? AND id != ?" , parentID , parentID ) .
Find ( & files ) . Error ; err != nil {
return err
}
for _ , f := range files {
size += f . Size
if f . Type == "folder" {
if err := dfs ( f . ID ) ; err != nil {
return err
}
}
}
return nil
}
if err := dfs ( folderID ) ; err != nil {
return 0 , err
}
return size , nil
}
// HasChildFolder checks if folder has child folders
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) HasChildFolder ( ctx context . Context , db * gorm . DB , folderID string ) ( bool , error ) {
2026-03-04 19:17:16 +08:00
var count int64
2026-07-24 20:19:41 +08:00
err := db . WithContext ( ctx ) . Model ( & entity . File { } ) .
2026-03-04 19:17:16 +08:00
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)
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) GetAllParentFolders ( ctx context . Context , db * gorm . DB , startID string ) ( [ ] * entity . File , error ) {
2026-03-27 19:25:18 +08:00
var parentFolders [ ] * entity . File
2026-03-04 19:17:16 +08:00
currentID := startID
for currentID != "" {
2026-03-27 19:25:18 +08:00
var file entity . File
2026-07-24 20:19:41 +08:00
err := db . WithContext ( ctx ) . Where ( "id = ?" , currentID ) . First ( & file ) . Error
2026-03-04 19:17:16 +08:00
if err != nil {
return nil , err
}
parentFolders = append ( parentFolders , & file )
// Stop if we've reached the root folder (parent_id == id)
if file . ParentID == file . ID {
break
}
currentID = file . ParentID
}
return parentFolders , nil
}
2026-03-06 16:42:49 +08:00
// Create creates a new file
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) Create ( ctx context . Context , db * gorm . DB , file * entity . File ) error {
return db . WithContext ( ctx ) . Create ( file ) . Error
2026-03-06 16:42:49 +08:00
}
2026-04-30 12:36:03 +08:00
// UpdateByID updates a file by ID
2026-07-24 20:19:41 +08:00
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
2026-04-30 12:36:03 +08:00
}
2026-03-13 16:53:54 +08:00
// DeleteByTenantID deletes all files by tenant ID (hard delete)
2026-07-24 20:19:41 +08:00
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 { } )
2026-03-13 16:53:54 +08:00
return result . RowsAffected , result . Error
}
// DeleteByIDs deletes files by IDs (hard delete)
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) DeleteByIDs ( ctx context . Context , db * gorm . DB , ids [ ] string ) ( int64 , error ) {
2026-03-13 16:53:54 +08:00
if len ( ids ) == 0 {
return 0 , nil
}
2026-07-24 20:19:41 +08:00
result := db . WithContext ( ctx ) . Unscoped ( ) . Where ( "id IN ?" , ids ) . Delete ( & entity . File { } )
2026-03-13 16:53:54 +08:00
return result . RowsAffected , result . Error
}
// GetAllIDsByTenantID gets all file IDs by tenant ID
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) GetAllIDsByTenantID ( ctx context . Context , db * gorm . DB , tenantID string ) ( [ ] string , error ) {
2026-03-13 16:53:54 +08:00
var ids [ ] string
2026-07-24 20:19:41 +08:00
err := db . WithContext ( ctx ) . Model ( & entity . File { } ) . Where ( "tenant_id = ?" , tenantID ) . Pluck ( "id" , & ids ) . Error
2026-03-13 16:53:54 +08:00
return ids , err
}
2026-04-10 12:15:27 +08:00
// GetByIDs gets files by multiple IDs
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) GetByIDs ( ctx context . Context , db * gorm . DB , ids [ ] string ) ( [ ] * entity . File , error ) {
2026-04-10 12:15:27 +08:00
var files [ ] * entity . File
if len ( ids ) == 0 {
return files , nil
}
2026-07-24 20:19:41 +08:00
err := db . WithContext ( ctx ) . Where ( "id IN ?" , ids ) . Find ( & files ) . Error
2026-04-10 12:15:27 +08:00
return files , err
}
// ListAllFilesByParentID lists all files by parent folder ID
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) ListAllFilesByParentID ( ctx context . Context , db * gorm . DB , parentID string ) ( [ ] * entity . File , error ) {
2026-04-10 12:15:27 +08:00
var files [ ] * entity . File
2026-07-24 20:19:41 +08:00
err := db . WithContext ( ctx ) . Where ( "parent_id = ? AND id != ?" , parentID , parentID ) . Find ( & files ) . Error
2026-04-10 12:15:27 +08:00
return files , err
}
2026-06-15 11:19:56 +08:00
// ListNonFolderByParentID lists non-folder files directly under a parent folder.
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) ListNonFolderByParentID ( ctx context . Context , db * gorm . DB , parentID string ) ( [ ] * entity . File , error ) {
2026-06-15 11:19:56 +08:00
var files [ ] * entity . File
2026-07-24 20:19:41 +08:00
err := db . WithContext ( ctx ) . Where ( "parent_id = ? AND id != ? AND type != ?" , parentID , parentID , "folder" ) . Find ( & files ) . Error
2026-06-15 11:19:56 +08:00
return files , err
}
// ListFolderByParentID lists sub-folders directly under a parent folder.
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) ListFolderByParentID ( ctx context . Context , db * gorm . DB , parentID string ) ( [ ] * entity . File , error ) {
2026-06-15 11:19:56 +08:00
var files [ ] * entity . File
2026-07-24 20:19:41 +08:00
err := db . WithContext ( ctx ) . Where ( "parent_id = ? AND type = ?" , parentID , "folder" ) . Find ( & files ) . Error
2026-06-15 11:19:56 +08:00
return files , err
}
2026-04-02 20:21:04 +08:00
// GetByParentIDAndName gets file by parent folder ID and name
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) GetByParentIDAndName ( ctx context . Context , db * gorm . DB , parentID , name string ) ( * entity . File , error ) {
2026-04-02 20:21:04 +08:00
var file entity . File
2026-07-24 20:19:41 +08:00
err := db . WithContext ( ctx ) . Where ( "parent_id = ? AND name = ?" , parentID , name ) . First ( & file ) . Error
2026-04-02 20:21:04 +08:00
if err != nil {
return nil , err
}
return & file , nil
}
// GetIDListByID recursively gets list of file IDs by traversing folder structure
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) GetIDListByID ( ctx context . Context , db * gorm . DB , id string , names [ ] string , count int , res [ ] string ) ( [ ] string , error ) {
2026-04-02 20:21:04 +08:00
if count < len ( names ) {
2026-07-24 20:19:41 +08:00
file , err := dao . GetByParentIDAndName ( ctx , db , id , names [ count ] )
2026-04-02 20:21:04 +08:00
if err != nil {
return res , nil
}
res = append ( res , file . ID )
2026-07-24 20:19:41 +08:00
return dao . GetIDListByID ( ctx , db , file . ID , names , count + 1 , res )
2026-04-02 20:21:04 +08:00
}
return res , nil
}
// CreateFolder creates a folder in the database
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) CreateFolder ( ctx context . Context , db * gorm . DB , parentID , tenantID , name , fileType string ) ( * entity . File , error ) {
2026-04-02 20:21:04 +08:00
file := & entity . File {
2026-07-07 17:22:08 +08:00
ID : utility . GenerateToken ( ) ,
2026-04-02 20:21:04 +08:00
ParentID : parentID ,
TenantID : tenantID ,
CreatedBy : tenantID ,
Name : name ,
Type : fileType ,
Size : 0 ,
SourceType : "" ,
}
2026-07-24 20:19:41 +08:00
if err := db . WithContext ( ctx ) . Create ( file ) . Error ; err != nil {
2026-04-02 20:21:04 +08:00
return nil , err
}
return file , nil
}
// Insert inserts a new file record
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) Insert ( ctx context . Context , db * gorm . DB , file * entity . File ) error {
return db . WithContext ( ctx ) . Create ( file ) . Error
2026-04-02 20:21:04 +08:00
}
// IsParentFolderExist checks if parent folder exists
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) IsParentFolderExist ( ctx context . Context , db * gorm . DB , parentID string ) bool {
2026-04-02 20:21:04 +08:00
var count int64
2026-07-24 20:19:41 +08:00
err := db . WithContext ( ctx ) . Model ( & entity . File { } ) . Where ( "id = ?" , parentID ) . Count ( & count ) . Error
2026-04-02 20:21:04 +08:00
if err != nil || count == 0 {
return false
}
return true
}
// Query retrieves files by conditions
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) Query ( ctx context . Context , db * gorm . DB , name string , parentID string , tenantID string ) ( [ ] * entity . File , error ) {
2026-04-02 20:21:04 +08:00
var files [ ] * entity . File
2026-07-24 20:19:41 +08:00
query := db . WithContext ( ctx ) . Model ( & entity . File { } )
2026-04-02 20:21:04 +08:00
if name != "" {
query = query . Where ( "name = ?" , name )
}
if parentID != "" {
query = query . Where ( "parent_id = ?" , parentID )
}
2026-07-02 19:54:22 +08:00
if tenantID != "" {
query = query . Where ( "tenant_id = ?" , tenantID )
}
2026-07-24 20:19:41 +08:00
if err := query . Find ( & files ) . Error ; err != nil {
return nil , err
}
return files , nil
2026-04-02 20:21:04 +08:00
}
2026-04-10 12:15:27 +08:00
// Delete deletes a file by ID (hard delete)
2026-07-24 20:19:41 +08:00
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
2026-04-10 12:15:27 +08:00
}
// GetDatasetIDByFileID gets dataset ID by file ID
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) GetDatasetIDByFileID ( ctx context . Context , db * gorm . DB , fileID string ) ( [ ] string , error ) {
2026-04-10 12:15:27 +08:00
var datasetIDs [ ] string
2026-07-24 20:19:41 +08:00
rows , err := db . WithContext ( ctx ) . Model ( & entity . File { } ) .
2026-04-10 12:15:27 +08:00
Select ( "knowledgebase.id" ) .
Joins ( "JOIN file2document ON file2document.file_id = ?" , fileID ) .
Joins ( "JOIN document ON document.id = file2document.document_id" ) .
Joins ( "JOIN knowledgebase ON knowledgebase.id = document.kb_id" ) .
Where ( "file.id = ?" , fileID ) .
Rows ( )
if err != nil {
return nil , err
}
defer rows . Close ( )
for rows . Next ( ) {
var kbID string
if err := rows . Scan ( & kbID ) ; err != nil {
continue
}
datasetIDs = append ( datasetIDs , kbID )
}
return datasetIDs , nil
2026-04-02 20:21:04 +08:00
}
2026-07-03 12:06:45 +08:00
// reparentAndDeleteFolder safely removes a duplicate folder by first
// 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.
2026-07-24 20:19:41 +08:00
func reparentAndDeleteFolder ( ctx context . Context , db * gorm . DB , dupID , keepID string ) error {
2026-07-03 12:06:45 +08:00
// Reparent any child files/folders from the duplicate to the kept folder
2026-07-24 20:19:41 +08:00
if err := db . WithContext ( ctx ) . Model ( & entity . File { } ) .
2026-07-03 12:06:45 +08:00
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
2026-07-24 20:19:41 +08:00
if err := db . WithContext ( ctx ) . Unscoped ( ) . Where ( "id = ?" , dupID ) . Delete ( & entity . File { } ) . Error ; err != nil {
2026-07-03 12:06:45 +08:00
return fmt . Errorf ( "failed to delete duplicate folder %s: %w" , dupID , err )
}
return nil
}
2026-04-10 12:15:27 +08:00
// DatasetFolderName is the folder name for dataset
const DatasetFolderName = ".knowledgebase"
2026-04-03 15:08:43 +08:00
2026-07-03 12:06:45 +08:00
// InitDatasetDocs initializes dataset documents for tenant.
// This matches Python's FileService.init_dataset_docs method.
// Deduplicates duplicate entries that may have been created by
// concurrent race conditions (TOCTOU).
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) InitDatasetDocs ( ctx context . Context , db * gorm . DB , rootID , tenantID string , file2DocumentDAO * File2DocumentDAO ) error {
2026-07-03 12:06:45 +08:00
var existing [ ] * entity . File
2026-07-24 20:19:41 +08:00
err := db . WithContext ( ctx ) . Where ( "name = ? AND parent_id = ? AND tenant_id = ?" , DatasetFolderName , rootID , tenantID ) .
2026-07-03 12:06:45 +08:00
Order ( "create_time ASC" ) .
Find ( & existing ) . Error
2026-04-03 15:08:43 +08:00
if err != nil {
return err
}
2026-07-03 12:06:45 +08:00
if len ( existing ) > 0 {
if len ( existing ) > 1 {
log . Printf ( "[WARN] Found %d duplicate '%s' folders under root %s, keeping only the first" ,
len ( existing ) , DatasetFolderName , rootID )
keepID := existing [ 0 ] . ID
for _ , dup := range existing [ 1 : ] {
2026-07-24 20:19:41 +08:00
if err := reparentAndDeleteFolder ( ctx , db , dup . ID , keepID ) ; err != nil {
2026-07-03 12:06:45 +08:00
log . Printf ( "[ERROR] Failed to deduplicate folder %s: %v" , dup . ID , err )
}
}
}
2026-04-03 15:08:43 +08:00
return nil
}
2026-07-24 20:19:41 +08:00
datasetFolder , err := dao . newAFileFromDataset ( ctx , db , tenantID , DatasetFolderName , rootID )
2026-04-03 15:08:43 +08:00
if err != nil {
return err
}
2026-04-10 12:15:27 +08:00
var datasets [ ] entity . Knowledgebase
2026-07-24 20:19:41 +08:00
err = db . WithContext ( ctx ) . Select ( "id" , "name" ) .
2026-04-03 15:08:43 +08:00
Where ( "tenant_id = ?" , tenantID ) .
2026-04-10 12:15:27 +08:00
Find ( & datasets ) . Error
2026-04-03 15:08:43 +08:00
if err != nil {
return err
}
2026-04-10 12:15:27 +08:00
for _ , ds := range datasets {
2026-07-24 20:19:41 +08:00
var datasetFolderForDataset * entity . File
datasetFolderForDataset , err = dao . newAFileFromDataset ( ctx , db , tenantID , ds . Name , datasetFolder . ID )
2026-04-03 15:08:43 +08:00
if err != nil {
continue
}
var documents [ ] entity . Document
2026-07-24 20:19:41 +08:00
err = db . WithContext ( ctx ) . Where ( "kb_id = ?" , ds . ID ) . Find ( & documents ) . Error
2026-04-03 15:08:43 +08:00
if err != nil {
continue
}
for _ , doc := range documents {
2026-07-24 20:19:41 +08:00
if err = dao . addFileFromKB ( ctx , db , & doc , datasetFolderForDataset . ID , tenantID , file2DocumentDAO ) ; err != nil {
2026-04-10 12:15:27 +08:00
return err
}
2026-04-03 15:08:43 +08:00
}
}
return nil
}
2026-07-03 12:06:45 +08:00
// newAFileFromDataset creates a new file from knowledgebase, or returns the existing one.
// Deduplicates duplicate entries that may have been created by race conditions.
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) newAFileFromDataset ( ctx context . Context , db * gorm . DB , tenantID , name , parentID string ) ( * entity . File , error ) {
2026-04-03 15:08:43 +08:00
var existingFiles [ ] * entity . File
2026-07-24 20:19:41 +08:00
err := db . WithContext ( ctx ) . Where ( "tenant_id = ? AND parent_id = ? AND name = ?" , tenantID , parentID , name ) . Order ( "create_time ASC" ) . Find ( & existingFiles ) . Error
2026-04-03 15:08:43 +08:00
if err != nil {
return nil , err
}
if len ( existingFiles ) > 0 {
2026-07-03 12:06:45 +08:00
if len ( existingFiles ) > 1 {
log . Printf ( "[WARN] Found %d duplicate entries named '%s' under parent %s, keeping only the first" ,
len ( existingFiles ) , name , parentID )
keepID := existingFiles [ 0 ] . ID
for _ , dup := range existingFiles [ 1 : ] {
2026-07-24 20:19:41 +08:00
if err = reparentAndDeleteFolder ( ctx , db , dup . ID , keepID ) ; err != nil {
2026-07-03 12:06:45 +08:00
log . Printf ( "[ERROR] Failed to deduplicate file entry %s: %v" , dup . ID , err )
}
}
}
2026-04-03 15:08:43 +08:00
return existingFiles [ 0 ] , nil
}
2026-07-07 17:22:08 +08:00
fileID := utility . GenerateToken ( )
2026-04-03 15:08:43 +08:00
file := & entity . File {
ID : fileID ,
ParentID : parentID ,
TenantID : tenantID ,
CreatedBy : tenantID ,
Name : name ,
Type : "folder" ,
Size : 0 ,
SourceType : "knowledgebase" ,
}
2026-07-24 20:19:41 +08:00
if err = db . WithContext ( ctx ) . Create ( file ) . Error ; err != nil {
2026-04-03 15:08:43 +08:00
return nil , err
}
return file , nil
}
// addFileFromKB adds a file record from knowledgebase document
2026-07-24 20:19:41 +08:00
func ( dao * FileDAO ) addFileFromKB ( ctx context . Context , db * gorm . DB , doc * entity . Document , datasetFolderID , tenantID string , file2DocumentDAO * File2DocumentDAO ) error {
2026-04-03 15:08:43 +08:00
var f2dCount int64
2026-07-24 20:19:41 +08:00
err := db . WithContext ( ctx ) . Model ( & entity . File2Document { } ) .
2026-04-03 15:08:43 +08:00
Where ( "document_id = ?" , doc . ID ) .
Count ( & f2dCount ) . Error
if err != nil {
return err
}
if f2dCount > 0 {
return nil
}
docName := ""
if doc . Name != nil {
docName = * doc . Name
}
docLocation := ""
if doc . Location != nil {
docLocation = * doc . Location
}
2026-07-07 17:22:08 +08:00
fileID := utility . GenerateToken ( )
2026-04-03 15:08:43 +08:00
file := & entity . File {
ID : fileID ,
2026-04-10 12:15:27 +08:00
ParentID : datasetFolderID ,
2026-04-03 15:08:43 +08:00
TenantID : tenantID ,
CreatedBy : tenantID ,
Name : docName ,
Type : doc . Type ,
Size : doc . Size ,
Location : & docLocation ,
SourceType : "knowledgebase" ,
}
2026-07-24 20:19:41 +08:00
if err = db . WithContext ( ctx ) . Create ( file ) . Error ; err != nil {
2026-04-03 15:08:43 +08:00
return err
}
2026-07-07 17:22:08 +08:00
f2dID := utility . GenerateToken ( )
2026-04-03 15:08:43 +08:00
f2d := & entity . File2Document {
ID : f2dID ,
FileID : & fileID ,
DocumentID : & doc . ID ,
}
2026-07-24 20:19:41 +08:00
if err = db . WithContext ( ctx ) . Create ( f2d ) . Error ; err != nil {
2026-04-03 15:08:43 +08:00
return err
}
return nil
}