Files
hpc/internal/service/folder_service.go
dailz f0847d3978 feat(service): add upload, download, file, and folder services
Add UploadService (dedup, chunk lifecycle, ComposeObject), DownloadService (Range support), FileService (ref counting), FolderService (path validation).

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-15 09:23:09 +08:00

143 lines
3.9 KiB
Go

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
}