d9535ac053
modified: serv_nginx/api_bb/internal/handlers/user.go modified: serv_nginx/api_bb/internal/routes/routes.go modified: serv_nginx/api_bb/internal/service/avatar_service.go set all avatars manipulating into avatar.go and remove from user.go
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
|
|
}
|