Defines repo interfaces in biz (dependency inversion) implemented by data layer. Removes old domain layer replaced by data layer in previous commit.
147 lines
4.5 KiB
Go
147 lines
4.5 KiB
Go
package biz
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"time"
|
|
|
|
"rag/file-system/internal/data"
|
|
|
|
"github.com/go-kratos/kratos/v2/log"
|
|
)
|
|
|
|
// ShareRepo defines the interface for share link persistence operations.
|
|
type ShareRepo interface {
|
|
Create(ctx context.Context, share *data.ShareLinkPO) error
|
|
GetByToken(ctx context.Context, token string) (*data.ShareLinkPO, error)
|
|
GetByID(ctx context.Context, id string) (*data.ShareLinkPO, error)
|
|
Delete(ctx context.Context, id, createdBy string) error
|
|
IncrementDownloadCount(ctx context.Context, token string) error
|
|
ListByResource(ctx context.Context, resourceType, resourceID string) ([]data.ShareLinkPO, error)
|
|
}
|
|
|
|
// FileMetaRepoForShare is a minimal interface for share usecase to look up file metadata.
|
|
type FileMetaRepoForShare interface {
|
|
GetByID(ctx context.Context, id string) (*data.FileMetaPO, error)
|
|
}
|
|
|
|
// ShareUsecase handles share link business logic.
|
|
type ShareUsecase struct {
|
|
shareRepo ShareRepo
|
|
fileRepo FileMetaRepoForShare
|
|
s3Repo FileRepo
|
|
log *log.Helper
|
|
}
|
|
|
|
// NewShareUsecase creates a new ShareUsecase.
|
|
func NewShareUsecase(shareRepo ShareRepo, fileRepo FileMetaRepoForShare, s3Repo FileRepo, logger log.Logger) *ShareUsecase {
|
|
return &ShareUsecase{
|
|
shareRepo: shareRepo,
|
|
fileRepo: fileRepo,
|
|
s3Repo: s3Repo,
|
|
log: log.NewHelper(logger),
|
|
}
|
|
}
|
|
|
|
// CreateShare creates a new share link with a random token.
|
|
func (uc *ShareUsecase) CreateShare(ctx context.Context, resourceType, resourceID, password string, expiresAt *time.Time, maxDownloads *int, createdBy string) (*data.ShareLinkPO, error) {
|
|
token, err := generateToken(16)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate share token: %w", err)
|
|
}
|
|
|
|
share := &data.ShareLinkPO{
|
|
ResourceType: resourceType,
|
|
ResourceID: resourceID,
|
|
Token: token,
|
|
ExpiresAt: expiresAt,
|
|
MaxDownloads: maxDownloads,
|
|
CreatedBy: createdBy,
|
|
}
|
|
if password != "" {
|
|
pwd := password
|
|
share.Password = &pwd
|
|
}
|
|
|
|
if err := uc.shareRepo.Create(ctx, share); err != nil {
|
|
return nil, fmt.Errorf("failed to create share link: %w", err)
|
|
}
|
|
return share, nil
|
|
}
|
|
|
|
// DeleteShare removes a share link by ID and creator.
|
|
func (uc *ShareUsecase) DeleteShare(ctx context.Context, id, createdBy string) error {
|
|
return uc.shareRepo.Delete(ctx, id, createdBy)
|
|
}
|
|
|
|
// GetShareInfo retrieves share details and the associated file metadata.
|
|
func (uc *ShareUsecase) GetShareInfo(ctx context.Context, token string) (*data.ShareLinkPO, *data.FileMetaPO, error) {
|
|
share, err := uc.shareRepo.GetByToken(ctx, token)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to get share link: %w", err)
|
|
}
|
|
if share == nil {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
fileMeta, err := uc.fileRepo.GetByID(ctx, share.ResourceID)
|
|
if err != nil {
|
|
return share, nil, fmt.Errorf("failed to get file metadata: %w", err)
|
|
}
|
|
return share, fileMeta, nil
|
|
}
|
|
|
|
// DownloadShare validates the share link, increments download count, and returns a presigned URL + filename.
|
|
func (uc *ShareUsecase) DownloadShare(ctx context.Context, token string) (string, string, error) {
|
|
share, err := uc.shareRepo.GetByToken(ctx, token)
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("failed to get share link: %w", err)
|
|
}
|
|
if share == nil {
|
|
return "", "", fmt.Errorf("share link not found")
|
|
}
|
|
|
|
// Check expiry.
|
|
if share.ExpiresAt != nil && share.ExpiresAt.Before(time.Now().UTC()) {
|
|
return "", "", fmt.Errorf("share link has expired")
|
|
}
|
|
|
|
// Check max downloads.
|
|
if share.MaxDownloads != nil && share.DownloadCount >= *share.MaxDownloads {
|
|
return "", "", fmt.Errorf("download limit reached")
|
|
}
|
|
|
|
// Get file metadata for bucket/key info.
|
|
fileMeta, err := uc.fileRepo.GetByID(ctx, share.ResourceID)
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("failed to get file metadata: %w", err)
|
|
}
|
|
if fileMeta == nil {
|
|
return "", "", fmt.Errorf("file not found")
|
|
}
|
|
|
|
// Generate presigned URL.
|
|
url, err := uc.s3Repo.GeneratePresignedURL(ctx, fileMeta.S3Bucket, fileMeta.S3Key, 15*time.Minute)
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("failed to generate download URL: %w", err)
|
|
}
|
|
|
|
// Increment download count.
|
|
if err := uc.shareRepo.IncrementDownloadCount(ctx, token); err != nil {
|
|
uc.log.Errorf("failed to increment download count for token %s: %v", token, err)
|
|
}
|
|
|
|
return url, fileMeta.Name, nil
|
|
}
|
|
|
|
// generateToken creates a cryptographically secure random hex token.
|
|
func generateToken(byteLen int) (string, error) {
|
|
b := make([]byte, byteLen)
|
|
if _, err := rand.Read(b); err != nil {
|
|
return "", err
|
|
}
|
|
return hex.EncodeToString(b), nil
|
|
}
|