From 4927de90cc6bb3e9e12b347a3ab5674c9a0dfe75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=91=E5=AE=81?= <1772105645@qq.com> Date: Mon, 25 May 2026 13:00:48 +0800 Subject: [PATCH] feat: add biz layer with usecases for file, bucket, folder, share Defines repo interfaces in biz (dependency inversion) implemented by data layer. Removes old domain layer replaced by data layer in previous commit. --- internal/biz/biz.go | 10 ++ internal/biz/bucket.go | 36 ++++ internal/biz/file.go | 97 +++++++++++ internal/biz/folder.go | 155 ++++++++++++++++++ internal/biz/share.go | 146 +++++++++++++++++ internal/domain/model/file_meta.go | 16 -- internal/domain/model/folder.go | 18 -- internal/domain/model/share_link.go | 25 --- .../domain/repository/file_meta_repository.go | 14 -- internal/domain/repository/file_repository.go | 44 ----- .../domain/repository/folder_repository.go | 16 -- .../domain/repository/share_repository.go | 15 -- 12 files changed, 444 insertions(+), 148 deletions(-) create mode 100644 internal/biz/biz.go create mode 100644 internal/biz/bucket.go create mode 100644 internal/biz/file.go create mode 100644 internal/biz/folder.go create mode 100644 internal/biz/share.go delete mode 100644 internal/domain/model/file_meta.go delete mode 100644 internal/domain/model/folder.go delete mode 100644 internal/domain/model/share_link.go delete mode 100644 internal/domain/repository/file_meta_repository.go delete mode 100644 internal/domain/repository/file_repository.go delete mode 100644 internal/domain/repository/folder_repository.go delete mode 100644 internal/domain/repository/share_repository.go diff --git a/internal/biz/biz.go b/internal/biz/biz.go new file mode 100644 index 0000000..f95c803 --- /dev/null +++ b/internal/biz/biz.go @@ -0,0 +1,10 @@ +package biz + +import "github.com/google/wire" + +var ProviderSet = wire.NewSet( + NewFileUsecase, + NewBucketUsecase, + NewFolderUsecase, + NewShareUsecase, +) diff --git a/internal/biz/bucket.go b/internal/biz/bucket.go new file mode 100644 index 0000000..6f9a435 --- /dev/null +++ b/internal/biz/bucket.go @@ -0,0 +1,36 @@ +package biz + +import ( + "context" + + "github.com/go-kratos/kratos/v2/log" +) + +// BucketUsecase wraps FileRepo bucket operations. +type BucketUsecase struct { + repo FileRepo + log *log.Helper +} + +// NewBucketUsecase creates a new BucketUsecase. +func NewBucketUsecase(repo FileRepo, logger log.Logger) *BucketUsecase { + return &BucketUsecase{ + repo: repo, + log: log.NewHelper(logger), + } +} + +// ListBuckets returns all bucket names. +func (uc *BucketUsecase) ListBuckets(ctx context.Context) ([]string, error) { + return uc.repo.ListBuckets(ctx) +} + +// CreateBucket creates a new S3 bucket. +func (uc *BucketUsecase) CreateBucket(ctx context.Context, name string) error { + return uc.repo.CreateBucket(ctx, name) +} + +// DeleteBucket deletes an S3 bucket. +func (uc *BucketUsecase) DeleteBucket(ctx context.Context, name string) error { + return uc.repo.DeleteBucket(ctx, name) +} diff --git a/internal/biz/file.go b/internal/biz/file.go new file mode 100644 index 0000000..d7f1412 --- /dev/null +++ b/internal/biz/file.go @@ -0,0 +1,97 @@ +package biz + +import ( + "context" + "io" + "time" + + "rag/file-system/internal/data" + + "github.com/go-kratos/kratos/v2/log" +) + +// FileRepo defines the interface for S3 storage operations. +type FileRepo interface { + UploadFile(ctx context.Context, bucket, key string, fileData io.Reader) error + DownloadFile(ctx context.Context, bucket, key string) (io.ReadCloser, error) + ListBuckets(ctx context.Context) ([]string, error) + CreateBucket(ctx context.Context, name string) error + DeleteBucket(ctx context.Context, name string) error + ListObjectsV2(ctx context.Context, bucket, prefix string, maxKeys int32, token *string) (*data.ListFilesResult, error) + GeneratePresignedURL(ctx context.Context, bucket, key string, expiry time.Duration) (string, error) + GetFileContent(ctx context.Context, bucket, key string) (string, error) + DeleteFile(ctx context.Context, bucket, key string) error + CreateMultipartUpload(ctx context.Context, bucket, key string) (string, error) + UploadPart(ctx context.Context, bucket, key, uploadID string, partNumber int32, fileData io.Reader) (string, error) + CompleteMultipartUpload(ctx context.Context, bucket, key, uploadID string, parts []data.Part) (string, error) + AbortMultipartUpload(ctx context.Context, bucket, key, uploadID string) error +} + +// FileUsecase wraps FileRepo and provides file-level business operations. +type FileUsecase struct { + repo FileRepo + log *log.Helper +} + +// NewFileUsecase creates a new FileUsecase. +func NewFileUsecase(repo FileRepo, logger log.Logger) *FileUsecase { + return &FileUsecase{ + repo: repo, + log: log.NewHelper(logger), + } +} + +// UploadFile uploads data to the specified bucket and key. +func (uc *FileUsecase) UploadFile(ctx context.Context, bucket, key string, fileData io.Reader) error { + return uc.repo.UploadFile(ctx, bucket, key, fileData) +} + +// DownloadFile downloads an object from S3. +func (uc *FileUsecase) DownloadFile(ctx context.Context, bucket, key string) (io.ReadCloser, error) { + return uc.repo.DownloadFile(ctx, bucket, key) +} + +// GetFileContent retrieves text content for preview. +func (uc *FileUsecase) GetFileContent(ctx context.Context, bucket, key string) (string, error) { + return uc.repo.GetFileContent(ctx, bucket, key) +} + +// DeleteFile removes a file from S3. +func (uc *FileUsecase) DeleteFile(ctx context.Context, bucket, key string) error { + return uc.repo.DeleteFile(ctx, bucket, key) +} + +// ListObjectsV2 lists files with pagination support. +func (uc *FileUsecase) ListObjectsV2(ctx context.Context, bucket, prefix string, maxKeys int32, token *string) (*data.ListFilesResult, error) { + return uc.repo.ListObjectsV2(ctx, bucket, prefix, maxKeys, token) +} + +// GeneratePresignedURL generates a presigned URL with custom expiry. +func (uc *FileUsecase) GeneratePresignedURL(ctx context.Context, bucket, key string, expiry time.Duration) (string, error) { + return uc.repo.GeneratePresignedURL(ctx, bucket, key, expiry) +} + +// GetPreviewURL generates a presigned URL with 24h expiry for file preview. +func (uc *FileUsecase) GetPreviewURL(ctx context.Context, bucket, key string) (string, error) { + return uc.repo.GeneratePresignedURL(ctx, bucket, key, 24*time.Hour) +} + +// CreateMultipartUpload initializes a multipart upload session. +func (uc *FileUsecase) CreateMultipartUpload(ctx context.Context, bucket, key string) (string, error) { + return uc.repo.CreateMultipartUpload(ctx, bucket, key) +} + +// UploadPart uploads a single part of a multipart upload. +func (uc *FileUsecase) UploadPart(ctx context.Context, bucket, key, uploadID string, partNumber int32, fileData io.Reader) (string, error) { + return uc.repo.UploadPart(ctx, bucket, key, uploadID, partNumber, fileData) +} + +// CompleteMultipartUpload assembles all parts to complete the upload. +func (uc *FileUsecase) CompleteMultipartUpload(ctx context.Context, bucket, key, uploadID string, parts []data.Part) (string, error) { + return uc.repo.CompleteMultipartUpload(ctx, bucket, key, uploadID, parts) +} + +// AbortMultipartUpload cancels an in-progress multipart upload. +func (uc *FileUsecase) AbortMultipartUpload(ctx context.Context, bucket, key, uploadID string) error { + return uc.repo.AbortMultipartUpload(ctx, bucket, key, uploadID) +} diff --git a/internal/biz/folder.go b/internal/biz/folder.go new file mode 100644 index 0000000..1cb05d7 --- /dev/null +++ b/internal/biz/folder.go @@ -0,0 +1,155 @@ +package biz + +import ( + "bytes" + "context" + "fmt" + "time" + + "rag/file-system/internal/data" + "rag/file-system/internal/pkg/sanitize" + + "github.com/go-kratos/kratos/v2/log" + "github.com/google/uuid" +) + +// FolderRepo defines the interface for folder persistence operations. +type FolderRepo interface { + Create(ctx context.Context, folder *data.FolderPO) error + GetByID(ctx context.Context, id string) (*data.FolderPO, error) + GetWithChildren(ctx context.Context, id, ownerID string) (*data.FolderWithChildren, error) + GetTree(ctx context.Context, ownerID string) ([]data.FolderPO, error) + Update(ctx context.Context, folder *data.FolderPO) error + Delete(ctx context.Context, id, ownerID string) error + GetDescendantFileS3Keys(ctx context.Context, id, ownerID string) ([]data.FileMetaPO, error) +} + +// FileMetaRepo defines the interface for file metadata persistence operations. +type FileMetaRepo interface { + Create(ctx context.Context, file *data.FileMetaPO) error + GetByID(ctx context.Context, id string) (*data.FileMetaPO, error) + GetByFolder(ctx context.Context, folderID string) ([]data.FileMetaPO, error) + Move(ctx context.Context, fileID, targetFolderID, ownerID string) error + Delete(ctx context.Context, id, ownerID string) error +} + +// FolderUsecase handles folder and file metadata business logic. +type FolderUsecase struct { + folderRepo FolderRepo + fileRepo FileMetaRepo + s3Repo FileRepo + log *log.Helper +} + +// NewFolderUsecase creates a new FolderUsecase. +func NewFolderUsecase(folderRepo FolderRepo, fileRepo FileMetaRepo, s3Repo FileRepo, logger log.Logger) *FolderUsecase { + return &FolderUsecase{ + folderRepo: folderRepo, + fileRepo: fileRepo, + s3Repo: s3Repo, + log: log.NewHelper(logger), + } +} + +// CreateFolder creates a new folder under the given parent. +func (uc *FolderUsecase) CreateFolder(ctx context.Context, parentID *string, name, ownerID string) (*data.FolderPO, error) { + folder := &data.FolderPO{ + ParentID: parentID, + Name: sanitize.Filename(name), + OwnerID: ownerID, + } + if err := uc.folderRepo.Create(ctx, folder); err != nil { + return nil, fmt.Errorf("failed to create folder: %w", err) + } + return folder, nil +} + +// GetFolder retrieves a folder with its children (sub-folders and files). +func (uc *FolderUsecase) GetFolder(ctx context.Context, id, ownerID string) (*data.FolderPO, []data.FolderPO, []data.FileMetaPO, error) { + result, err := uc.folderRepo.GetWithChildren(ctx, id, ownerID) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get folder: %w", err) + } + if result == nil { + return nil, nil, nil, nil + } + return &result.Folder, result.SubFolders, result.Files, nil +} + +// GetFolderTree retrieves all folders owned by the given owner. +func (uc *FolderUsecase) GetFolderTree(ctx context.Context, ownerID string) ([]data.FolderPO, error) { + return uc.folderRepo.GetTree(ctx, ownerID) +} + +// RenameFolder updates a folder's name. +func (uc *FolderUsecase) RenameFolder(ctx context.Context, id, name, ownerID string) (*data.FolderPO, error) { + folder, err := uc.folderRepo.GetByID(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to get folder: %w", err) + } + if folder == nil || folder.OwnerID != ownerID { + return nil, fmt.Errorf("folder not found or not owned by user") + } + folder.Name = sanitize.Filename(name) + folder.UpdatedAt = time.Now().UTC() + if err := uc.folderRepo.Update(ctx, folder); err != nil { + return nil, fmt.Errorf("failed to rename folder: %w", err) + } + return folder, nil +} + +// DeleteFolder deletes a folder and all descendant files from both S3 and the database. +func (uc *FolderUsecase) DeleteFolder(ctx context.Context, id, ownerID string) error { + // First, get all descendant file S3 keys so we can delete from S3. + files, err := uc.folderRepo.GetDescendantFileS3Keys(ctx, id, ownerID) + if err != nil { + return fmt.Errorf("failed to get descendant files: %w", err) + } + + // Delete each file from S3. + for _, f := range files { + if delErr := uc.s3Repo.DeleteFile(ctx, f.S3Bucket, f.S3Key); delErr != nil { + uc.log.Errorf("failed to delete S3 object %s/%s: %v", f.S3Bucket, f.S3Key, delErr) + // Continue deleting other files even if one fails. + } + } + + // Delete the folder record (cascade should handle child files in DB). + if err := uc.folderRepo.Delete(ctx, id, ownerID); err != nil { + return fmt.Errorf("failed to delete folder: %w", err) + } + return nil +} + +// UploadToFolder uploads a file to a folder: generates a UUID S3 key, uploads to S3, saves metadata. +func (uc *FolderUsecase) UploadToFolder(ctx context.Context, folderID, fileName string, fileData []byte, contentType, ownerID string) (*data.FileMetaPO, error) { + s3Key := uuid.New().String() + safeName := sanitize.Filename(fileName) + bucket := "files" // Default bucket for folder-based uploads. + + if err := uc.s3Repo.UploadFile(ctx, bucket, s3Key, bytes.NewReader(fileData)); err != nil { + return nil, fmt.Errorf("failed to upload file to S3: %w", err) + } + + meta := &data.FileMetaPO{ + FolderID: folderID, + Name: safeName, + S3Key: s3Key, + S3Bucket: bucket, + Size: int64(len(fileData)), + ContentType: contentType, + OwnerID: ownerID, + } + if err := uc.fileRepo.Create(ctx, meta); err != nil { + // Attempt to clean up the S3 object on metadata save failure. + _ = uc.s3Repo.DeleteFile(ctx, bucket, s3Key) + return nil, fmt.Errorf("failed to save file metadata: %w", err) + } + return meta, nil +} + +// MoveFile moves a file from one folder to another. +func (uc *FolderUsecase) MoveFile(ctx context.Context, fileID, targetFolderID, ownerID string) error { + return uc.fileRepo.Move(ctx, fileID, targetFolderID, ownerID) +} + diff --git a/internal/biz/share.go b/internal/biz/share.go new file mode 100644 index 0000000..9aa80e9 --- /dev/null +++ b/internal/biz/share.go @@ -0,0 +1,146 @@ +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 +} diff --git a/internal/domain/model/file_meta.go b/internal/domain/model/file_meta.go deleted file mode 100644 index 914fc98..0000000 --- a/internal/domain/model/file_meta.go +++ /dev/null @@ -1,16 +0,0 @@ -package model - -import "time" - -type FileMeta struct { - ID string - FolderID string - Name string - S3Key string - S3Bucket string - Size int64 - ContentType string - OwnerID string - CreatedAt time.Time - UpdatedAt time.Time -} diff --git a/internal/domain/model/folder.go b/internal/domain/model/folder.go deleted file mode 100644 index 864c1f7..0000000 --- a/internal/domain/model/folder.go +++ /dev/null @@ -1,18 +0,0 @@ -package model - -import "time" - -type Folder struct { - ID string - ParentID *string - Name string - OwnerID string - CreatedAt time.Time - UpdatedAt time.Time -} - -type FolderWithChildren struct { - Folder Folder - SubFolders []Folder - Files []FileMeta -} diff --git a/internal/domain/model/share_link.go b/internal/domain/model/share_link.go deleted file mode 100644 index 9e9ae0b..0000000 --- a/internal/domain/model/share_link.go +++ /dev/null @@ -1,25 +0,0 @@ -package model - -import "time" - -type ShareLink struct { - ID string - ResourceType string - ResourceID string - Token string - Password *string - ExpiresAt *time.Time - DownloadCount int - MaxDownloads *int - CreatedBy string - CreatedAt time.Time -} - -type ShareInfo struct { - Token string - ResourceType string - FileName string - FileSize int64 - HasPassword bool - ExpiresAt *time.Time -} diff --git a/internal/domain/repository/file_meta_repository.go b/internal/domain/repository/file_meta_repository.go deleted file mode 100644 index b21898b..0000000 --- a/internal/domain/repository/file_meta_repository.go +++ /dev/null @@ -1,14 +0,0 @@ -package repository - -import ( - "context" - "rag/file-system/internal/domain/model" -) - -type FileMetaRepository interface { - Create(ctx context.Context, file *model.FileMeta) error - GetByID(ctx context.Context, id string) (*model.FileMeta, error) - GetByFolder(ctx context.Context, folderID string) ([]model.FileMeta, error) - Move(ctx context.Context, fileID string, targetFolderID string, ownerID string) error - Delete(ctx context.Context, id string, ownerID string) error -} diff --git a/internal/domain/repository/file_repository.go b/internal/domain/repository/file_repository.go deleted file mode 100644 index 0f865a8..0000000 --- a/internal/domain/repository/file_repository.go +++ /dev/null @@ -1,44 +0,0 @@ -package repository - -import ( - "context" - "rag/file-system/internal/common" - "io" - "time" -) - -type FileInfo struct { - Key string - Size int64 - LastModified time.Time - ETag string -} - -type ListFilesResult struct { - Files []FileInfo - NextContinuationToken *string -} - -type FileRepository interface { - UploadFile(ctx context.Context, bucketName string, objectKey string, data io.Reader) error - DownloadFile(ctx context.Context, bucketName string, objectKey string) (io.ReadCloser, error) - ListBuckets(ctx context.Context) ([]string, error) - CreateBucket(ctx context.Context, bucketName string) error - DeleteBucket(ctx context.Context, bucketName string) error - - // File listing with pagination - ListObjectsV2(ctx context.Context, bucketName string, prefix string, maxKeys int32, continuationToken *string) (*ListFilesResult, error) - GeneratePresignedURL(ctx context.Context, bucketName string, objectKey string, expiry time.Duration) (string, error) - - // File content retrieval for text preview - GetFileContent(ctx context.Context, bucketName string, objectKey string) (string, error) - - // File deletion - DeleteFile(ctx context.Context, bucketName string, objectKey string) error - - // Multipart upload - CreateMultipartUpload(ctx context.Context, bucketName string, objectKey string) (string, error) - UploadPart(ctx context.Context, bucketName string, objectKey string, uploadId string, partNumber int32, data io.Reader) (string, error) - CompleteMultipartUpload(ctx context.Context, bucketName string, objectKey string, uploadId string, parts []common.Part) (string, error) - AbortMultipartUpload(ctx context.Context, bucketName string, objectKey string, uploadId string) error -} diff --git a/internal/domain/repository/folder_repository.go b/internal/domain/repository/folder_repository.go deleted file mode 100644 index 3b83b48..0000000 --- a/internal/domain/repository/folder_repository.go +++ /dev/null @@ -1,16 +0,0 @@ -package repository - -import ( - "context" - "rag/file-system/internal/domain/model" -) - -type FolderRepository interface { - Create(ctx context.Context, folder *model.Folder) error - GetByID(ctx context.Context, id string) (*model.Folder, error) - GetWithChildren(ctx context.Context, id string, ownerID string) (*model.FolderWithChildren, error) - GetTree(ctx context.Context, ownerID string) ([]model.Folder, error) - Update(ctx context.Context, folder *model.Folder) error - Delete(ctx context.Context, id string, ownerID string) error - GetDescendantFileS3Keys(ctx context.Context, id string, ownerID string) ([]model.FileMeta, error) -} diff --git a/internal/domain/repository/share_repository.go b/internal/domain/repository/share_repository.go deleted file mode 100644 index 6e10d96..0000000 --- a/internal/domain/repository/share_repository.go +++ /dev/null @@ -1,15 +0,0 @@ -package repository - -import ( - "context" - "rag/file-system/internal/domain/model" -) - -type ShareRepository interface { - Create(ctx context.Context, share *model.ShareLink) error - GetByToken(ctx context.Context, token string) (*model.ShareLink, error) - GetByID(ctx context.Context, id string) (*model.ShareLink, error) - Delete(ctx context.Context, id string, createdBy string) error - IncrementDownloadCount(ctx context.Context, token string) error - ListByResource(ctx context.Context, resourceType string, resourceID string) ([]model.ShareLink, error) -}