Replace old infrastructure layer with Kratos-style data layer: - data.go: GORM connection, transaction support, Wire ProviderSet, PO models - file_repo.go: All 12 S3 operations (upload, download, multipart, presign, buckets) - folder_repo.go: GORM queries including recursive CTE for descendant files - file_meta_repo.go: CRUD + move operations for file metadata - share_repo.go: CRUD + increment download count for share links Deleted old infrastructure/database, infrastructure/repository, infrastructure/s3. Kept infrastructure/grpc for later integration.
149 lines
4.3 KiB
Go
149 lines
4.3 KiB
Go
package data
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/go-kratos/kratos/v2/log"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// FolderRepo implements folder persistence operations using GORM.
|
|
type FolderRepo struct {
|
|
data *Data
|
|
log *log.Helper
|
|
}
|
|
|
|
// NewFolderRepo creates a new FolderRepo.
|
|
func NewFolderRepo(data *Data, logger log.Logger) *FolderRepo {
|
|
return &FolderRepo{
|
|
data: data,
|
|
log: log.NewHelper(logger),
|
|
}
|
|
}
|
|
|
|
// FolderWithChildren holds a folder along with its sub-folders and files.
|
|
type FolderWithChildren struct {
|
|
Folder FolderPO
|
|
SubFolders []FolderPO
|
|
Files []FileMetaPO
|
|
}
|
|
|
|
// Create inserts a new folder record.
|
|
func (r *FolderRepo) Create(ctx context.Context, folder *FolderPO) error {
|
|
return r.data.DB(ctx).Create(folder).Error
|
|
}
|
|
|
|
// GetByID retrieves a folder by its ID. Returns nil if not found.
|
|
func (r *FolderRepo) GetByID(ctx context.Context, id string) (*FolderPO, error) {
|
|
var folder FolderPO
|
|
err := r.data.DB(ctx).Where("id = ?", id).First(&folder).Error
|
|
if err != nil {
|
|
if isRecordNotFound(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("failed to get folder: %w", err)
|
|
}
|
|
return &folder, nil
|
|
}
|
|
|
|
// GetWithChildren retrieves a folder with its sub-folders and files, filtered by ownerID.
|
|
func (r *FolderRepo) GetWithChildren(ctx context.Context, id string, ownerID string) (*FolderWithChildren, error) {
|
|
folder, err := r.GetByID(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if folder == nil || folder.OwnerID != ownerID {
|
|
return nil, nil
|
|
}
|
|
|
|
var subFolders []FolderPO
|
|
if err := r.data.DB(ctx).
|
|
Where("parent_id = ? AND owner_id = ?", id, ownerID).
|
|
Order("name").
|
|
Find(&subFolders).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to query sub-folders: %w", err)
|
|
}
|
|
|
|
var files []FileMetaPO
|
|
if err := r.data.DB(ctx).
|
|
Where("folder_id = ? AND owner_id = ?", id, ownerID).
|
|
Order("name").
|
|
Find(&files).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to query files: %w", err)
|
|
}
|
|
|
|
return &FolderWithChildren{
|
|
Folder: *folder,
|
|
SubFolders: subFolders,
|
|
Files: files,
|
|
}, nil
|
|
}
|
|
|
|
// GetTree retrieves all folders owned by the given ownerID.
|
|
func (r *FolderRepo) GetTree(ctx context.Context, ownerID string) ([]FolderPO, error) {
|
|
var folders []FolderPO
|
|
err := r.data.DB(ctx).
|
|
Where("owner_id = ?", ownerID).
|
|
Order("name").
|
|
Find(&folders).Error
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get folder tree: %w", err)
|
|
}
|
|
return folders, nil
|
|
}
|
|
|
|
// Update modifies a folder's name and updated_at timestamp.
|
|
func (r *FolderRepo) Update(ctx context.Context, folder *FolderPO) error {
|
|
result := r.data.DB(ctx).Model(&FolderPO{}).
|
|
Where("id = ?", folder.ID).
|
|
Updates(map[string]interface{}{
|
|
"name": folder.Name,
|
|
"updated_at": folder.UpdatedAt,
|
|
})
|
|
if result.Error != nil {
|
|
return fmt.Errorf("failed to update folder: %w", result.Error)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Delete removes a folder by ID and ownerID. Returns error if not found.
|
|
func (r *FolderRepo) Delete(ctx context.Context, id string, ownerID string) error {
|
|
result := r.data.DB(ctx).
|
|
Where("id = ? AND owner_id = ?", id, ownerID).
|
|
Delete(&FolderPO{})
|
|
if result.Error != nil {
|
|
return fmt.Errorf("failed to delete folder: %w", result.Error)
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
return fmt.Errorf("folder not found or not owned by user")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetDescendantFileS3Keys returns all files in a folder and all its descendant folders.
|
|
// Uses a recursive CTE to walk the folder tree.
|
|
func (r *FolderRepo) GetDescendantFileS3Keys(ctx context.Context, id string, ownerID string) ([]FileMetaPO, error) {
|
|
var files []FileMetaPO
|
|
// Raw SQL with recursive CTE - GORM doesn't natively support recursive queries
|
|
err := r.data.DB(ctx).Raw(`
|
|
WITH RECURSIVE descendants AS (
|
|
SELECT id FROM folders WHERE id = ? AND owner_id = ?
|
|
UNION
|
|
SELECT f.id FROM folders f INNER JOIN descendants d ON f.parent_id = d.id
|
|
)
|
|
SELECT fi.id, fi.folder_id, fi.name, fi.s3_key, fi.s3_bucket, fi.size, fi.content_type, fi.owner_id, fi.created_at, fi.updated_at
|
|
FROM files fi INNER JOIN descendants d ON fi.folder_id = d.id
|
|
WHERE fi.owner_id = ?`,
|
|
id, ownerID, ownerID).Scan(&files).Error
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get descendant file S3 keys: %w", err)
|
|
}
|
|
return files, nil
|
|
}
|
|
|
|
// isRecordNotFound checks if the error is a GORM record-not-found error.
|
|
func isRecordNotFound(err error) bool {
|
|
return err == gorm.ErrRecordNotFound
|
|
}
|