modified: serv_nginx/api_bb/internal/handlers/avatar.go

modified:   serv_nginx/api_bb/internal/handlers/handlers.go
	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
	modified:   serv_nginx/nginx/nginx-ssl.conf
try to serve file name throught path
This commit is contained in:
2025-10-14 12:41:16 +05:00
parent 46549f5d22
commit bbf470617b
6 changed files with 226 additions and 30 deletions
@@ -3,6 +3,7 @@ package handlers
import (
"net/http"
"time"
"api_bb/internal/service"
"api_bb/pkg/logger"
@@ -27,8 +28,12 @@ func NewAvatarHandler(avatarService service.AvatarService) *AvatarHandler {
func (h *AvatarHandler) Routes() chi.Router {
r := chi.NewRouter()
// r.Get("/avatar/{filename}", h.ServeAvatar)
r.Post("/upload", h.UploadAvatar)
r.Delete("/delete", h.DeleteAvatar)
r.Get("/{filename}", h.GetAvatar)
return r
}
@@ -99,3 +104,43 @@ func (h *AvatarHandler) DeleteAvatar(w http.ResponseWriter, r *http.Request) {
"message": "Avatar deleted successfully",
})
}
// GET /v1/user/avatar/avatar_22_1760417314.png
func (h *AvatarHandler) GetAvatar(w http.ResponseWriter, r *http.Request) {
filename := chi.URLParam(r, "filename")
h.logger.Info("handling get avatar request",
zap.String("method", r.Method),
zap.String("filename", filename),
zap.String("remote_addr", r.RemoteAddr),
)
// Вариант 1: Используем ServeAvatarFile (более эффективно для больших файлов)
contentType, err := h.avatarService.ServeAvatarFile(w, filename)
if err != nil {
h.logger.Warn("failed to serve avatar file",
zap.String("filename", filename),
zap.Error(err),
)
switch {
case err.Error() == "avatar file not found":
utils.RespondWithError(w, http.StatusNotFound, "Avatar not found")
case err.Error() == "invalid filename" || err.Error() == "unsupported file format":
utils.RespondWithError(w, http.StatusBadRequest, err.Error())
default:
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to serve avatar")
}
return
}
// Устанавливаем заголовки
w.Header().Set("Content-Type", contentType)
w.Header().Set("Cache-Control", "public, max-age=31536000") // Кэш на 1 год
w.Header().Set("Expires", time.Now().Add(365*24*time.Hour).Format(http.TimeFormat))
h.logger.Info("avatar served successfully",
zap.String("filename", filename),
zap.String("content_type", contentType),
)
}
@@ -14,6 +14,8 @@ type Handler struct {
healthHandler *HealthHandler
authHandler *AuthHandler
userHandler *UserHandler
avatarHandler *AvatarHandler
newsHandler *NewsHandler
// Здесь будут добавлены другие обработчики
// userHandler *UserHandler
// eventHandler *EventHandler
@@ -23,6 +25,8 @@ type Handler struct {
func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
// Инициализация репозиториев
userRepo := repository.NewUserRepository(db)
newsRepo := repository.NewNewsRepository(db)
commentRepo := repository.NewCommentRepository(db)
// Initialize logger
baseLogger := logger.NewWrapper(logger.Get()) // Создаем базовый логгер
@@ -31,16 +35,22 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
jwtService := service.NewJWTService(cfg.JWTSecret)
authService := service.NewAuthService(userRepo, jwtService, baseLogger)
userService := service.NewUserService(userRepo, jwtService, baseLogger)
avatarService := service.NewAvatarService(userRepo, baseLogger)
newsService := service.NewNewsService(newsRepo, commentRepo, baseLogger)
// Инициализация обработчиков
healthHandler := NewHealthHandler()
authHandler := NewAuthHandler(authService, jwtService)
userHandler := NewUserHandler(&userService)
newsHandler := NewNewsHandler(newsService, baseLogger)
avatarHandler := NewAvatarHandler(avatarService)
return &Handler{
healthHandler: healthHandler,
authHandler: authHandler,
userHandler: userHandler,
newsHandler: newsHandler,
avatarHandler: avatarHandler,
}
}
@@ -56,3 +66,11 @@ func (h *Handler) AuthHandler() *AuthHandler {
func (h *Handler) UserHandler() *UserHandler { // ДОБАВЛЕН геттер для UserHandler
return h.userHandler
}
func (h *Handler) AvatarHandler() *AvatarHandler {
return h.avatarHandler
}
func (h *Handler) NewsHandler() *NewsHandler {
return h.newsHandler
}
+34 -1
View File
@@ -6,6 +6,8 @@ import (
"encoding/json"
"io"
"net/http"
"os"
"path/filepath"
"time"
"api_bb/internal/models"
@@ -35,6 +37,7 @@ func (h *UserHandler) Routes() chi.Router {
r.Get("/profile", h.GetProfile)
r.Post("/editProfile", h.UpdateProfile)
r.Get("/avatar/{filename}", h.ServeAvatar)
return r
}
@@ -54,7 +57,6 @@ type UserResponse struct {
UpdatedAt time.Time `json:"updated_at"`
}
func (h *UserHandler) GetProfile(w http.ResponseWriter, r *http.Request) {
h.logger.Info("handling get profile request",
@@ -176,3 +178,34 @@ func (h *UserHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
"user": toUserResponse(updatedUser),
})
}
func (h *UserHandler) ServeAvatar(w http.ResponseWriter, r *http.Request) {
filename := chi.URLParam(r, "filename")
h.logger.Info("handling serve avatar request",
zap.String("method", r.Method),
zap.String("filename", filename),
zap.String("remote_addr", r.RemoteAddr),
)
// Используем http.ServeFile для эффективной отдачи файла
avatarPath := filepath.Join("./uploads/avatars", filename)
// Проверяем существование файла
if _, err := os.Stat(avatarPath); os.IsNotExist(err) {
h.logger.Warn("avatar file not found", zap.String("path", avatarPath))
utils.RespondWithError(w, http.StatusNotFound, "Avatar not found")
return
}
// Устанавливаем заголовки кэширования
w.Header().Set("Cache-Control", "public, max-age=31536000")
w.Header().Set("Expires", time.Now().Add(365*24*time.Hour).Format(http.TimeFormat))
// Отдаем файл
http.ServeFile(w, r, avatarPath)
h.logger.Info("avatar served via ServeFile",
zap.String("filename", filename),
)
}