Files
test_deploy/internal/file_manager/s3_storage/storage.go
Alex Shevchuk d84487d238 1
2025-08-18 17:12:04 +03:00

157 lines
4.5 KiB
Go

package s3_storage
import (
"bytes"
"context"
"errors"
"fmt"
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/cache"
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
filemanager "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/file_manager"
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/object_storage"
"github.com/aws/smithy-go/ptr"
"mime"
"mime/multipart"
"path/filepath"
"strings"
"time"
)
type S3Storage struct {
objectStorage object_storage.Client
cacheClient cache.Client
}
func NewS3Storage(objectStorage object_storage.Client, cacheClient cache.Client) *S3Storage {
return &S3Storage{
objectStorage: objectStorage,
cacheClient: cacheClient,
}
}
func (s *S3Storage) GetFilePaths(
ctx context.Context,
fileType filemanager.FileType,
parameters filemanager.ParameterTable,
) ([]string, error) {
objectKey, category, err := s.buildObjectKey(fileType, parameters)
if err != nil {
return nil, err
}
// Сначала пытаемся получить URL из кэша
cachedURL, err := s.getCachedURL(ctx, objectKey, category)
if err == nil && cachedURL != "" {
return []string{cachedURL}, nil
}
// Получаем линк из s3
url, err := s.objectStorage.GetPresignedLink(ctx, objectKey, category,
object_storage.LinkOptions{
TTL: ptr.Duration(constants.DefaultFileTTL),
})
if err != nil {
if errors.Is(err, object_storage.ErrObjectNotFound) {
return nil, filemanager.ErrFileNotFound
}
return nil, fmt.Errorf("%s: %w", filemanager.ErrMsgGetPresignedURL, err)
}
// Кэшируем URL
if err := s.cacheURL(ctx, objectKey, category, url); err != nil {
return nil, fmt.Errorf("%s: %w", filemanager.ErrFailedToCacheUrl, err)
}
return []string{url}, nil
}
func (s *S3Storage) SaveFile(
ctx context.Context,
fileType filemanager.FileType,
file multipart.File,
fileHeader *multipart.FileHeader,
parameters filemanager.ParameterTable,
) error {
objectKey, category, err := s.buildObjectKey(fileType, parameters)
if err != nil {
return err
}
buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(file); err != nil {
return fmt.Errorf("%s: %w", filemanager.ErrMsgReadFile, err)
}
// Определяем тип контента
contentType := s.getContentType(fileHeader.Filename)
// Загружаем файл в S3
err = s.objectStorage.PutNewObject(ctx, objectKey, category,
bytes.NewReader(buf.Bytes()),
object_storage.PutOptions{
ContentType: contentType,
})
if err != nil {
// Обрабатываем случай существующего файла
if errors.Is(err, object_storage.ErrObjectAlreadyExists) {
return filemanager.ErrFileAlreadyExists
}
return fmt.Errorf("%s: %w", filemanager.ErrFailedUploadFile, err)
}
return nil
}
// Создаем ключ объекта S3 на основе типа файла и параметров
func (s *S3Storage) buildObjectKey(fileType filemanager.FileType, parameters filemanager.ParameterTable) (string, object_storage.Category, error) {
switch fileType {
case filemanager.AvatarFileType:
userID, ok := parameters[filemanager.UserIdParam]
if !ok {
return "", "", filemanager.ErrParameterNotFound
}
return fmt.Sprintf(objectKeyAvatarTemplate, userID), object_storage.LogoCategory, nil
case filemanager.CVFileType:
submissionID, ok := parameters[filemanager.SubmissionIdParam]
if !ok {
return "", "", filemanager.ErrParameterNotFound
}
return fmt.Sprintf(objectKeyCVTemplate, submissionID), object_storage.DocumentCategory, nil
default:
return "", "", filemanager.ErrInvalidFileType
}
}
// getContentType определяет тип контента по расширению
func (s *S3Storage) getContentType(filename string) string {
ext := strings.ToLower(filepath.Ext(filename))
if ct, ok := ContentTypes[ext]; ok {
return ct
}
if ct := mime.TypeByExtension(ext); ct != "" {
return ct
}
return defaultContentType
}
func (s *S3Storage) getCachedURL(ctx context.Context, objectKey string, category object_storage.Category) (string, error) {
cacheKey := fmt.Sprintf(cacheKeyTemplate, category, objectKey)
return s.cacheClient.Get(ctx, cacheKey, cache.DocumentsValueType)
}
func (s *S3Storage) cacheURL(ctx context.Context, objectKey string, category object_storage.Category, url string) error {
cacheKey := fmt.Sprintf(cacheKeyTemplate, category, objectKey)
cacheDuration := constants.DefaultFileTTL - time.Hour
return s.cacheClient.Set(ctx, cacheKey, cache.DocumentsValueType, url, cacheDuration)
}