package folder import ( "context" "fmt" "queryorchestration/internal/database/repository" "queryorchestration/internal/serviceconfig" "github.com/google/uuid" ) // Service provides folder management operations type Service struct { cfg serviceconfig.ConfigProvider } // New creates a new folder service instance func New(cfg serviceconfig.ConfigProvider) *Service { return &Service{ cfg: cfg, } } // CreateFolder creates a new folder with validation // Validates that parent folder exists if parentId is provided func (s *Service) CreateFolder(ctx context.Context, path string, parentID *uuid.UUID, clientID string, createdBy string) (*repository.Folder, error) { if path == "" { return nil, fmt.Errorf("folder path cannot be empty") } if clientID == "" { return nil, fmt.Errorf("clientID cannot be empty") } if createdBy == "" { return nil, fmt.Errorf("createdBy cannot be empty") } // Validate parent folder exists if parentID is provided if parentID != nil { parent, err := s.cfg.GetDBQueries().GetFolderByID(ctx, *parentID) if err != nil { return nil, fmt.Errorf("parent folder not found: %w", err) } // Ensure parent belongs to same client if parent.Clientid != clientID { return nil, fmt.Errorf("parent folder belongs to different client") } } folder, err := s.cfg.GetDBQueries().CreateFolder(ctx, &repository.CreateFolderParams{ Path: path, Parentid: parentID, Clientid: clientID, Createdby: createdBy, }) if err != nil { return nil, fmt.Errorf("failed to create folder: %w", err) } return folder, nil } // GetFolder retrieves a folder by ID func (s *Service) GetFolder(ctx context.Context, folderID uuid.UUID) (*repository.Folder, error) { folder, err := s.cfg.GetDBQueries().GetFolderByID(ctx, folderID) if err != nil { return nil, fmt.Errorf("folder not found: %w", err) } return folder, nil } // GetFolderByPath retrieves a folder by client and path func (s *Service) GetFolderByPath(ctx context.Context, clientID string, path string) (*repository.Folder, error) { folder, err := s.cfg.GetDBQueries().GetFolderByPath(ctx, &repository.GetFolderByPathParams{ Clientid: clientID, Path: path, }) if err != nil { return nil, fmt.Errorf("folder not found: %w", err) } return folder, nil } // RenameFolder renames a folder func (s *Service) RenameFolder(ctx context.Context, folderID uuid.UUID, newPath string) error { if newPath == "" { return fmt.Errorf("new path cannot be empty") } // Verify folder exists _, err := s.cfg.GetDBQueries().GetFolderByID(ctx, folderID) if err != nil { return fmt.Errorf("folder not found: %w", err) } err = s.cfg.GetDBQueries().RenameFolder(ctx, &repository.RenameFolderParams{ ID: folderID, Path: newPath, }) if err != nil { return fmt.Errorf("failed to rename folder: %w", err) } return nil } // GetFolderTree retrieves a folder and all its descendants (recursive) func (s *Service) GetFolderTree(ctx context.Context, folderID uuid.UUID) ([]*repository.GetFolderTreeRow, error) { folders, err := s.cfg.GetDBQueries().GetFolderTree(ctx, &folderID) if err != nil { return nil, fmt.Errorf("failed to get folder tree: %w", err) } return folders, nil } // GetFoldersByClient retrieves all folders for a client func (s *Service) GetFoldersByClient(ctx context.Context, clientID string) ([]*repository.Folder, error) { folders, err := s.cfg.GetDBQueries().GetFoldersByClientID(ctx, clientID) if err != nil { return nil, fmt.Errorf("failed to get folders: %w", err) } return folders, nil } // GetRootFolders retrieves all root folders (parentId IS NULL) for a client func (s *Service) GetRootFolders(ctx context.Context, clientID string) ([]*repository.Folder, error) { folders, err := s.cfg.GetDBQueries().GetRootFolders(ctx, clientID) if err != nil { return nil, fmt.Errorf("failed to get root folders: %w", err) } return folders, nil } // GetSubfolders retrieves direct child folders of a parent folder func (s *Service) GetSubfolders(ctx context.Context, parentID uuid.UUID) ([]*repository.Folder, error) { folders, err := s.cfg.GetDBQueries().GetFoldersByParentID(ctx, &parentID) if err != nil { return nil, fmt.Errorf("failed to get subfolders: %w", err) } return folders, nil } // GetDocumentsByFolder retrieves all documents in a specific folder func (s *Service) GetDocumentsByFolder(ctx context.Context, folderID uuid.UUID) ([]*repository.Document, error) { documents, err := s.cfg.GetDBQueries().GetDocumentsByFolder(ctx, &folderID) if err != nil { return nil, fmt.Errorf("failed to get documents by folder: %w", err) } return documents, nil } // FolderMetrics contains aggregated metrics for a folder type FolderMetrics struct { TotalDocuments int32 ByLabel map[string]int32 } // GetFolderMetrics retrieves processing metrics for a folder including document counts by label func (s *Service) GetFolderMetrics(ctx context.Context, folderID uuid.UUID) (*FolderMetrics, error) { // Get total document count total, err := s.cfg.GetDBQueries().CountDocumentsByFolder(ctx, &folderID) if err != nil { return nil, fmt.Errorf("failed to count documents: %w", err) } // Get label counts labelCounts, err := s.cfg.GetDBQueries().GetFolderLabelCounts(ctx, &folderID) if err != nil { return nil, fmt.Errorf("failed to get label counts: %w", err) } byLabel := make(map[string]int32) for _, lc := range labelCounts { byLabel[lc.Label] = safeInt64ToInt32(lc.Count) } return &FolderMetrics{ TotalDocuments: safeInt64ToInt32(total), ByLabel: byLabel, }, nil } // safeInt64ToInt32 safely converts int64 to int32, clamping at max int32 if necessary func safeInt64ToInt32(n int64) int32 { const maxInt32 = 2147483647 if n > maxInt32 { return maxInt32 } return int32(n) //nolint:gosec // bounds check performed above }