feat(store): add blob, file, folder, and upload stores

Add BlobStore (ref counting), FileStore (soft delete + pagination), FolderStore (materialized path), UploadStore (idempotent upsert), and update AutoMigrate.

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
dailz
2026-04-15 09:22:44 +08:00
parent c861ff3adf
commit bf89de12f0
9 changed files with 1442 additions and 1 deletions

View File

@@ -0,0 +1,105 @@
package store
import (
"context"
"errors"
"fmt"
"gcy_hpc_server/internal/model"
"gorm.io/gorm"
)
type FolderStore struct {
db *gorm.DB
}
func NewFolderStore(db *gorm.DB) *FolderStore {
return &FolderStore{db: db}
}
func (s *FolderStore) Create(ctx context.Context, folder *model.Folder) error {
return s.db.WithContext(ctx).Create(folder).Error
}
func (s *FolderStore) GetByID(ctx context.Context, id int64) (*model.Folder, error) {
var folder model.Folder
err := s.db.WithContext(ctx).First(&folder, id).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
if err != nil {
return nil, err
}
return &folder, nil
}
func (s *FolderStore) GetByPath(ctx context.Context, path string) (*model.Folder, error) {
var folder model.Folder
err := s.db.WithContext(ctx).Where("path = ?", path).First(&folder).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
if err != nil {
return nil, err
}
return &folder, nil
}
func (s *FolderStore) ListByParentID(ctx context.Context, parentID *int64) ([]model.Folder, error) {
var folders []model.Folder
query := s.db.WithContext(ctx)
if parentID == nil {
query = query.Where("parent_id IS NULL")
} else {
query = query.Where("parent_id = ?", *parentID)
}
if err := query.Order("name ASC").Find(&folders).Error; err != nil {
return nil, err
}
return folders, nil
}
// GetSubTree returns all folders whose path starts with the given prefix.
func (s *FolderStore) GetSubTree(ctx context.Context, path string) ([]model.Folder, error) {
var folders []model.Folder
if err := s.db.WithContext(ctx).Where("path LIKE ?", path+"%").Find(&folders).Error; err != nil {
return nil, err
}
return folders, nil
}
// HasChildren checks if a folder has sub-folders or files.
func (s *FolderStore) HasChildren(ctx context.Context, id int64) (bool, error) {
folder, err := s.GetByID(ctx, id)
if err != nil {
return false, err
}
if folder == nil {
return false, nil
}
// Check for sub-folders
var subFolderCount int64
if err := s.db.WithContext(ctx).Model(&model.Folder{}).Where("parent_id = ?", id).Count(&subFolderCount).Error; err != nil {
return false, fmt.Errorf("count sub-folders: %w", err)
}
if subFolderCount > 0 {
return true, nil
}
// Check for files
var fileCount int64
if err := s.db.WithContext(ctx).Model(&model.File{}).Where("folder_id = ?", id).Count(&fileCount).Error; err != nil {
return false, fmt.Errorf("count files: %w", err)
}
return fileCount > 0, nil
}
func (s *FolderStore) Delete(ctx context.Context, id int64) error {
result := s.db.WithContext(ctx).Delete(&model.Folder{}, id)
if result.Error != nil {
return result.Error
}
return nil
}