157 lines
4.5 KiB
Go
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)
|
|
}
|