diff --git a/serv_nginx/api_bb/internal/handlers/avatar.go b/serv_nginx/api_bb/internal/handlers/avatar.go index c81691a..7a5fe48 100644 --- a/serv_nginx/api_bb/internal/handlers/avatar.go +++ b/serv_nginx/api_bb/internal/handlers/avatar.go @@ -3,6 +3,7 @@ package handlers import ( "net/http" + "strings" "time" "api_bb/internal/service" @@ -35,7 +36,6 @@ func (h *AvatarHandler) Routes() chi.Router { zap.String("GET", "/{filename}"), ) - // r.Get("/avatar/{filename}", h.ServeAvatar) r.Post("/upload", h.UploadAvatar) r.Delete("/delete", h.DeleteAvatar) r.Get("/{filename}", h.GetAvatar) @@ -181,7 +181,7 @@ func (h *AvatarHandler) DeleteAvatar(w http.ResponseWriter, r *http.Request) { }) } -// GET /v1/user/avatar/avatar_22_1760417314.png +// GET /v1/user/avatars/{filename} func (h *AvatarHandler) GetAvatar(w http.ResponseWriter, r *http.Request) { filename := chi.URLParam(r, "filename") @@ -199,13 +199,20 @@ func (h *AvatarHandler) GetAvatar(w http.ResponseWriter, r *http.Request) { ) }() + // Валидация имени файла + if filename == "" || strings.Contains(filename, "..") || strings.Contains(filename, "/") { + h.logger.Warn("GetAvatar: invalid filename", zap.String("filename", filename)) + utils.RespondWithError(w, http.StatusBadRequest, "Invalid filename") + return + } + h.logger.Info("GetAvatar: handling get avatar request", zap.String("method", r.Method), zap.String("filename", filename), zap.String("remote_addr", r.RemoteAddr), ) - // Вариант 1: Используем ServeAvatarFile (более эффективно для больших файлов) + // Используем ServeAvatarFile для обслуживания файла h.logger.Debug("GetAvatar: calling avatarService.ServeAvatarFile", zap.String("filename", filename), ) @@ -233,7 +240,7 @@ func (h *AvatarHandler) GetAvatar(w http.ResponseWriter, r *http.Request) { return } - // Устанавливаем заголовки + // Устанавливаем заголовки для кэширования h.logger.Debug("GetAvatar: setting response headers", zap.String("content_type", contentType), ) @@ -245,4 +252,4 @@ func (h *AvatarHandler) GetAvatar(w http.ResponseWriter, r *http.Request) { zap.String("filename", filename), zap.String("content_type", contentType), ) -} +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/handlers/user.go b/serv_nginx/api_bb/internal/handlers/user.go index a92ba71..7b308bf 100644 --- a/serv_nginx/api_bb/internal/handlers/user.go +++ b/serv_nginx/api_bb/internal/handlers/user.go @@ -4,12 +4,8 @@ package handlers import ( "bytes" "encoding/json" - "fmt" "io" "net/http" - "os" - "path/filepath" - "strings" "time" "api_bb/internal/models" @@ -39,8 +35,7 @@ func (h *UserHandler) Routes() chi.Router { r.Get("/profile", h.GetProfile) r.Post("/editProfile", h.UpdateProfile) - r.Get("/avatars/{filename}", h.ServeAvatar) - + // Убрали маршрут для обслуживания аватаров - теперь это делает AvatarHandler return r } @@ -179,74 +174,4 @@ func (h *UserHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) { "message": "Profile updated successfully", "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), - ) - - // Валидация - if filename == "" || strings.Contains(filename, "..") || strings.Contains(filename, "/") { - utils.RespondWithError(w, http.StatusBadRequest, "Invalid filename") - return - } - - avatarPath := filepath.Join("./uploads/avatars", filename) - - // Проверяем существование файла - fileInfo, err := os.Stat(avatarPath) - if os.IsNotExist(err) { - h.logger.Warn("avatar file not found", zap.String("path", avatarPath)) - utils.RespondWithError(w, http.StatusNotFound, "Avatar not found") - return - } - if err != nil { - h.logger.Error("failed to access file", zap.Error(err)) - utils.RespondWithError(w, http.StatusInternalServerError, "File access error") - return - } - - // Открываем файл - file, err := os.Open(avatarPath) - if err != nil { - h.logger.Error("failed to open file", zap.Error(err)) - utils.RespondWithError(w, http.StatusInternalServerError, "File open error") - return - } - defer file.Close() - - // Определяем Content-Type - ext := strings.ToLower(filepath.Ext(filename)) - contentType := "application/octet-stream" - switch ext { - case ".png": contentType = "image/png" - case ".jpg", ".jpeg": contentType = "image/jpeg" - case ".gif": contentType = "image/gif" - case ".webp": contentType = "image/webp" - } - - // ✅ Устанавливаем ВСЕ заголовки - w.Header().Set("Content-Type", contentType) - w.Header().Set("Content-Length", fmt.Sprintf("%d", fileInfo.Size())) - w.Header().Set("Cache-Control", "public, max-age=31536000") // 1 год - w.Header().Set("Last-Modified", fileInfo.ModTime().Format(http.TimeFormat)) - - // Копируем файл в ResponseWriter - _, err = io.Copy(w, file) - if err != nil { - h.logger.Error("failed to send file", zap.Error(err)) - // Не отправляем ошибку - соединение уже испорчено - return - } - - h.logger.Info("avatar served successfully", - zap.String("filename", filename), - zap.String("content_type", contentType), - zap.Int64("file_size", fileInfo.Size()), - ) } \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/routes/routes.go b/serv_nginx/api_bb/internal/routes/routes.go index 15a7231..a1753a3 100644 --- a/serv_nginx/api_bb/internal/routes/routes.go +++ b/serv_nginx/api_bb/internal/routes/routes.go @@ -56,8 +56,9 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler { r.Use(middleware.AuthMiddleware(jwtService, userRepo)) r.Use(middleware.RequireAuth) - + // Все операции с аватарами теперь через AvatarHandler r.Mount("/avatars", allHandler.AvatarHandler().Routes()) + // Профиль пользователя r.Mount("/", allHandler.UserHandler().Routes()) // Здесь будут другие защищенные маршруты пользователя @@ -75,7 +76,7 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler { r.Group(func(r chi.Router) { r.Use(middleware.AuthMiddleware(jwtService, userRepo)) r.Use(middleware.RequireAuth) - + // News EndPoints r.Post("/", allHandler.NewsHandler().CreateNews) r.Put("/{id}", allHandler.NewsHandler().UpdateNews) diff --git a/serv_nginx/api_bb/internal/service/avatar_service.go b/serv_nginx/api_bb/internal/service/avatar_service.go index af62e90..41cf3df 100644 --- a/serv_nginx/api_bb/internal/service/avatar_service.go +++ b/serv_nginx/api_bb/internal/service/avatar_service.go @@ -19,7 +19,7 @@ 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) // НОВЫЙ МЕТОД + GetAvatarFile(filename string) ([]byte, string, error) ServeAvatarFile(w io.Writer, filename string) (string, error) }