package service import ( "context" "fmt" "gcy_hpc_server/internal/model" "gcy_hpc_server/internal/store" "go.uber.org/zap" ) // FolderService provides CRUD operations for folders with path validation // and directory tree management. type FolderService struct { folderStore *store.FolderStore fileStore *store.FileStore logger *zap.Logger } // NewFolderService creates a new FolderService. func NewFolderService(folderStore *store.FolderStore, fileStore *store.FileStore, logger *zap.Logger) *FolderService { return &FolderService{ folderStore: folderStore, fileStore: fileStore, logger: logger, } } // CreateFolder validates the name, computes a materialized path, checks for // duplicates, and persists the folder. func (s *FolderService) CreateFolder(ctx context.Context, name string, parentID *int64) (*model.FolderResponse, error) { if err := model.ValidateFolderName(name); err != nil { return nil, fmt.Errorf("invalid folder name: %w", err) } var path string if parentID == nil { path = "/" + name + "/" } else { parent, err := s.folderStore.GetByID(ctx, *parentID) if err != nil { return nil, fmt.Errorf("get parent folder: %w", err) } if parent == nil { return nil, fmt.Errorf("parent folder %d not found", *parentID) } path = parent.Path + name + "/" } existing, err := s.folderStore.GetByPath(ctx, path) if err != nil { return nil, fmt.Errorf("check duplicate path: %w", err) } if existing != nil { return nil, fmt.Errorf("folder with path %q already exists", path) } folder := &model.Folder{ Name: name, ParentID: parentID, Path: path, } if err := s.folderStore.Create(ctx, folder); err != nil { return nil, fmt.Errorf("create folder: %w", err) } return s.toFolderResponse(ctx, folder) } // GetFolder retrieves a folder by ID with file and subfolder counts. func (s *FolderService) GetFolder(ctx context.Context, id int64) (*model.FolderResponse, error) { folder, err := s.folderStore.GetByID(ctx, id) if err != nil { return nil, fmt.Errorf("get folder: %w", err) } if folder == nil { return nil, fmt.Errorf("folder %d not found", id) } return s.toFolderResponse(ctx, folder) } // ListFolders returns all direct children of the given parent folder (or root // if parentID is nil). func (s *FolderService) ListFolders(ctx context.Context, parentID *int64) ([]model.FolderResponse, error) { folders, err := s.folderStore.ListByParentID(ctx, parentID) if err != nil { return nil, fmt.Errorf("list folders: %w", err) } result := make([]model.FolderResponse, 0, len(folders)) for i := range folders { resp, err := s.toFolderResponse(ctx, &folders[i]) if err != nil { return nil, err } result = append(result, *resp) } return result, nil } // DeleteFolder soft-deletes a folder only if it has no children (sub-folders // or files). func (s *FolderService) DeleteFolder(ctx context.Context, id int64) error { hasChildren, err := s.folderStore.HasChildren(ctx, id) if err != nil { return fmt.Errorf("check children: %w", err) } if hasChildren { return fmt.Errorf("folder is not empty") } if err := s.folderStore.Delete(ctx, id); err != nil { return fmt.Errorf("delete folder: %w", err) } return nil } // toFolderResponse converts a Folder model into a FolderResponse DTO with // computed file and subfolder counts. func (s *FolderService) toFolderResponse(ctx context.Context, f *model.Folder) (*model.FolderResponse, error) { subFolders, err := s.folderStore.ListByParentID(ctx, &f.ID) if err != nil { return nil, fmt.Errorf("count subfolders: %w", err) } _, fileCount, err := s.fileStore.List(ctx, &f.ID, 1, 1) if err != nil { return nil, fmt.Errorf("count files: %w", err) } return &model.FolderResponse{ ID: f.ID, Name: f.Name, ParentID: f.ParentID, Path: f.Path, FileCount: fileCount, SubFolderCount: int64(len(subFolders)), CreatedAt: f.CreatedAt, }, nil }