15357fd3c0
yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarba
216 lines
6.0 KiB
Go
216 lines
6.0 KiB
Go
// service/avatar_service.go
|
|
package service
|
|
|
|
import (
|
|
"api_bb/internal/repository"
|
|
"api_bb/pkg/logger"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type AvatarService interface {
|
|
UploadAvatar(userID uint, file multipart.File, header *multipart.FileHeader) (string, error)
|
|
DeleteAvatar(userID uint) error
|
|
GetAvatarPath(userID uint) (string, error)
|
|
GetAvatarFile(filename string) ([]byte, string, error)
|
|
ServeAvatarFile(w io.Writer, filename string) (string, error)
|
|
}
|
|
|
|
type avatarService struct {
|
|
userRepo repository.UserRepository
|
|
logger logger.LoggerInterface
|
|
}
|
|
|
|
func NewAvatarService(userRepo repository.UserRepository, log logger.LoggerInterface) AvatarService {
|
|
return &avatarService{
|
|
userRepo: userRepo,
|
|
logger: log.With(zap.String("service", "avatar")),
|
|
}
|
|
}
|
|
|
|
func (s *avatarService) UploadAvatar(userID uint, file multipart.File, header *multipart.FileHeader) (string, error) {
|
|
// Проверяем пользователя
|
|
user, err := s.userRepo.FindByID(userID)
|
|
if err != nil {
|
|
return "", fmt.Errorf("user not found")
|
|
}
|
|
|
|
// Создаем директорию для аватаров если не существует
|
|
uploadDir := "./uploads/avatars"
|
|
if err := os.MkdirAll(uploadDir, 0755); err != nil {
|
|
return "", fmt.Errorf("failed to create upload directory: %v", err)
|
|
}
|
|
|
|
// Генерируем уникальное имя файла
|
|
fileExt := filepath.Ext(header.Filename)
|
|
fileName := fmt.Sprintf("avatar_%d_%d%s", userID, time.Now().Unix(), fileExt)
|
|
filePath := filepath.Join(uploadDir, fileName)
|
|
|
|
// Создаем файл
|
|
dst, err := os.Create(filePath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create file: %v", err)
|
|
}
|
|
defer dst.Close()
|
|
|
|
// Копируем содержимое
|
|
if _, err := io.Copy(dst, file); err != nil {
|
|
return "", fmt.Errorf("failed to save file: %v", err)
|
|
}
|
|
|
|
// Удаляем старый аватар если существует
|
|
if user.Avatar != "" {
|
|
oldPath := strings.TrimPrefix(user.Avatar, "/")
|
|
if _, err := os.Stat(oldPath); err == nil {
|
|
os.Remove(oldPath)
|
|
}
|
|
}
|
|
|
|
// Сохраняем путь в БД
|
|
avatarPath := "/uploads/avatars/" + fileName
|
|
if err := s.userRepo.UpdateAvatar(userID, avatarPath); err != nil {
|
|
// Если не удалось сохранить в БД, удаляем загруженный файл
|
|
os.Remove(filePath)
|
|
return "", fmt.Errorf("failed to update avatar in database: %v", err)
|
|
}
|
|
|
|
return avatarPath, nil
|
|
}
|
|
|
|
func (s *avatarService) DeleteAvatar(userID uint) error {
|
|
user, err := s.userRepo.FindByID(userID)
|
|
if err != nil {
|
|
return fmt.Errorf("user not found")
|
|
}
|
|
|
|
if user.Avatar == "" {
|
|
return nil // Аватара нет, ничего не делаем
|
|
}
|
|
|
|
// Удаляем файл
|
|
filePath := strings.TrimPrefix(user.Avatar, "/")
|
|
if _, err := os.Stat(filePath); err == nil {
|
|
if err := os.Remove(filePath); err != nil {
|
|
s.logger.Warn("Failed to delete avatar file", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
// Очищаем поле в БД
|
|
return s.userRepo.UpdateAvatar(userID, "")
|
|
}
|
|
|
|
func (s *avatarService) GetAvatarPath(userID uint) (string, error) {
|
|
user, err := s.userRepo.FindByID(userID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return user.Avatar, nil
|
|
}
|
|
|
|
func (s *avatarService) GetAvatarFile(filename string) ([]byte, string, error) {
|
|
// Валидация имени файла
|
|
if filename == "" || strings.Contains(filename, "..") || strings.Contains(filename, "/") {
|
|
return nil, "", fmt.Errorf("invalid filename")
|
|
}
|
|
|
|
// Проверяем допустимые расширения
|
|
allowedExts := map[string]string{
|
|
".png": "image/png",
|
|
".jpg": "image/jpeg",
|
|
".jpeg": "image/jpeg",
|
|
".gif": "image/gif",
|
|
".webp": "image/webp",
|
|
}
|
|
|
|
fileExt := strings.ToLower(filepath.Ext(filename))
|
|
contentType, exists := allowedExts[fileExt]
|
|
if !exists {
|
|
return nil, "", fmt.Errorf("unsupported file format")
|
|
}
|
|
|
|
// Формируем путь к файлу
|
|
filePath := filepath.Join("./uploads/avatars", filename)
|
|
|
|
// Проверяем существование файла
|
|
fileInfo, err := os.Stat(filePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, "", fmt.Errorf("avatar file not found")
|
|
}
|
|
return nil, "", fmt.Errorf("failed to access file: %v", err)
|
|
}
|
|
|
|
// Проверяем размер файла (максимум 10MB)
|
|
if fileInfo.Size() > 10*1024*1024 {
|
|
return nil, "", fmt.Errorf("file too large")
|
|
}
|
|
|
|
// Читаем файл
|
|
fileData, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("failed to read file: %v", err)
|
|
}
|
|
|
|
return fileData, contentType, nil
|
|
}
|
|
|
|
func (s *avatarService) ServeAvatarFile(w io.Writer, filename string) (string, error) {
|
|
// Валидация имени файла
|
|
if filename == "" || strings.Contains(filename, "..") || strings.Contains(filename, "/") {
|
|
return "", fmt.Errorf("invalid filename")
|
|
}
|
|
|
|
// Проверяем допустимые расширения
|
|
allowedExts := map[string]string{
|
|
".png": "image/png",
|
|
".jpg": "image/jpeg",
|
|
".jpeg": "image/jpeg",
|
|
".gif": "image/gif",
|
|
".webp": "image/webp",
|
|
}
|
|
|
|
fileExt := strings.ToLower(filepath.Ext(filename))
|
|
contentType, exists := allowedExts[fileExt]
|
|
if !exists {
|
|
return "", fmt.Errorf("unsupported file format")
|
|
}
|
|
|
|
// Формируем путь к файлу
|
|
filePath := filepath.Join("./uploads/avatars", filename)
|
|
|
|
// Проверяем существование файла
|
|
fileInfo, err := os.Stat(filePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return "", fmt.Errorf("avatar file not found")
|
|
}
|
|
return "", fmt.Errorf("failed to access file: %v", err)
|
|
}
|
|
|
|
// Проверяем размер файла
|
|
if fileInfo.Size() > 10*1024*1024 {
|
|
return "", fmt.Errorf("file too large")
|
|
}
|
|
|
|
// Открываем и копируем файл
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to open file: %v", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
_, err = io.Copy(w, file)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to serve file: %v", err)
|
|
}
|
|
|
|
return contentType, nil
|
|
}
|