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>
143 lines
3.9 KiB
Go
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
|
|
}
|