diff --git a/serv_nginx/api_bb/internal/handlers/avatar.go b/serv_nginx/api_bb/internal/handlers/avatar.go index 8e0ec3a..c81691a 100644 --- a/serv_nginx/api_bb/internal/handlers/avatar.go +++ b/serv_nginx/api_bb/internal/handlers/avatar.go @@ -28,8 +28,14 @@ func NewAvatarHandler(avatarService service.AvatarService) *AvatarHandler { func (h *AvatarHandler) Routes() chi.Router { r := chi.NewRouter() - - // r.Get("/avatar/{filename}", h.ServeAvatar) + + h.logger.Debug("Registering avatar routes", + zap.String("POST", "/upload"), + zap.String("DELETE", "/delete"), + 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) @@ -38,108 +44,204 @@ func (h *AvatarHandler) Routes() chi.Router { } func (h *AvatarHandler) UploadAvatar(w http.ResponseWriter, r *http.Request) { - user, ok := middleware.GetUserFromContext(r.Context()) - if !ok { - utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") - return - } + h.logger.Debug("UploadAvatar START", + zap.String("method", r.Method), + zap.String("path", r.URL.Path), + zap.String("remote_addr", r.RemoteAddr), + ) - // Парсим multipart форму - if err := r.ParseMultipartForm(10 << 20); err != nil { // 10MB limit - utils.RespondWithError(w, http.StatusBadRequest, "Failed to parse form: "+err.Error()) - return - } + defer func() { + h.logger.Debug("UploadAvatar END", + zap.String("method", r.Method), + zap.String("path", r.URL.Path), + ) + }() - file, header, err := r.FormFile("avatar") - if err != nil { - utils.RespondWithError(w, http.StatusBadRequest, "Failed to get file: "+err.Error()) - return - } - defer file.Close() + user, ok := middleware.GetUserFromContext(r.Context()) + if !ok { + h.logger.Warn("UploadAvatar: authentication required") + utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") + return + } - // Проверяем тип файла - allowedTypes := map[string]bool{ - "image/jpeg": true, - "image/jpg": true, - "image/png": true, - "image/gif": true, - } - if !allowedTypes[header.Header.Get("Content-Type")] { - utils.RespondWithError(w, http.StatusBadRequest, "Only JPEG, PNG and GIF images are allowed") - return - } + h.logger.Debug("UploadAvatar: user authenticated", + zap.Int64("user_id", int64(user.ID)), + zap.String("username", user.FirstName+user.LastName), + ) - // Загружаем аватар - avatarPath, err := h.avatarService.UploadAvatar(user.ID, file, header) - if err != nil { - h.logger.Error("Failed to upload avatar", zap.Error(err)) - utils.RespondWithError(w, http.StatusInternalServerError, "Failed to upload avatar: "+err.Error()) - return - } + // Парсим multipart форму + h.logger.Debug("UploadAvatar: parsing multipart form") + if err := r.ParseMultipartForm(10 << 20); err != nil { // 10MB limit + h.logger.Error("UploadAvatar: failed to parse form", zap.Error(err)) + utils.RespondWithError(w, http.StatusBadRequest, "Failed to parse form: "+err.Error()) + return + } - // Возвращаем ответ с полем success - utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{ - "success": true, - "message": "Avatar uploaded successfully", - "avatar": avatarPath, - }) + h.logger.Debug("UploadAvatar: getting file from form") + file, header, err := r.FormFile("avatar") + if err != nil { + h.logger.Error("UploadAvatar: failed to get file from form", zap.Error(err)) + utils.RespondWithError(w, http.StatusBadRequest, "Failed to get file: "+err.Error()) + return + } + defer file.Close() + + h.logger.Debug("UploadAvatar: file received", + zap.String("filename", header.Filename), + zap.Int64("size", header.Size), + zap.String("content_type", header.Header.Get("Content-Type")), + ) + + // Проверяем тип файла + allowedTypes := map[string]bool{ + "image/jpeg": true, + "image/jpg": true, + "image/png": true, + "image/gif": true, + } + contentType := header.Header.Get("Content-Type") + if !allowedTypes[contentType] { + h.logger.Warn("UploadAvatar: invalid file type", + zap.String("content_type", contentType), + zap.String("filename", header.Filename), + ) + utils.RespondWithError(w, http.StatusBadRequest, "Only JPEG, PNG and GIF images are allowed") + return + } + + h.logger.Debug("UploadAvatar: file type validated successfully") + + // Загружаем аватар + h.logger.Debug("UploadAvatar: calling avatarService.UploadAvatar", + zap.Int64("user_id", int64(user.ID)), + ) + avatarPath, err := h.avatarService.UploadAvatar(user.ID, file, header) + if err != nil { + h.logger.Error("UploadAvatar: failed to upload avatar", zap.Error(err)) + utils.RespondWithError(w, http.StatusInternalServerError, "Failed to upload avatar: "+err.Error()) + return + } + + h.logger.Info("UploadAvatar: avatar uploaded successfully", + zap.Int64("user_id", int64(user.ID)), + zap.String("avatar_path", avatarPath), + ) + + // Возвращаем ответ с полем success + utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{ + "success": true, + "message": "Avatar uploaded successfully", + "avatar": avatarPath, + }) } func (h *AvatarHandler) DeleteAvatar(w http.ResponseWriter, r *http.Request) { - user, ok := middleware.GetUserFromContext(r.Context()) - if !ok { - utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") - return - } + h.logger.Debug("DeleteAvatar START", + zap.String("method", r.Method), + zap.String("path", r.URL.Path), + zap.String("remote_addr", r.RemoteAddr), + ) - if err := h.avatarService.DeleteAvatar(user.ID); err != nil { - h.logger.Error("Failed to delete avatar", zap.Error(err)) - utils.RespondWithError(w, http.StatusInternalServerError, "Failed to delete avatar: "+err.Error()) - return - } + defer func() { + h.logger.Debug("DeleteAvatar END", + zap.String("method", r.Method), + zap.String("path", r.URL.Path), + ) + }() - // Возвращаем ответ с полем success - utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{ - "success": true, - "message": "Avatar deleted successfully", - }) + user, ok := middleware.GetUserFromContext(r.Context()) + if !ok { + h.logger.Warn("DeleteAvatar: authentication required") + utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") + return + } + + h.logger.Debug("DeleteAvatar: user authenticated", + zap.Int64("user_id", int64(user.ID)), + zap.String("username", user.FirstName+user.LastName), + ) + + h.logger.Debug("DeleteAvatar: calling avatarService.DeleteAvatar", + zap.Int64("user_id", int64(user.ID)), + ) + if err := h.avatarService.DeleteAvatar(user.ID); err != nil { + h.logger.Error("DeleteAvatar: failed to delete avatar", zap.Error(err)) + utils.RespondWithError(w, http.StatusInternalServerError, "Failed to delete avatar: "+err.Error()) + return + } + + h.logger.Info("DeleteAvatar: avatar deleted successfully", + zap.Int64("user_id", int64(user.ID)), + ) + + // Возвращаем ответ с полем success + utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{ + "success": true, + "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", + + h.logger.Debug("GetAvatar START", + zap.String("method", r.Method), + zap.String("filename", filename), + zap.String("remote_addr", r.RemoteAddr), + zap.String("url", r.URL.String()), + ) + + defer func() { + h.logger.Debug("GetAvatar END", + zap.String("method", r.Method), + zap.String("filename", filename), + ) + }() + + 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 (более эффективно для больших файлов) + h.logger.Debug("GetAvatar: calling avatarService.ServeAvatarFile", + zap.String("filename", filename), + ) contentType, err := h.avatarService.ServeAvatarFile(w, filename) if err != nil { - h.logger.Warn("failed to serve avatar file", + h.logger.Warn("GetAvatar: failed to serve avatar file", zap.String("filename", filename), zap.Error(err), ) - + switch { case err.Error() == "avatar file not found": + h.logger.Warn("GetAvatar: avatar file not found", zap.String("filename", filename)) utils.RespondWithError(w, http.StatusNotFound, "Avatar not found") case err.Error() == "invalid filename" || err.Error() == "unsupported file format": + h.logger.Warn("GetAvatar: invalid filename or format", + zap.String("filename", filename), + zap.Error(err), + ) utils.RespondWithError(w, http.StatusBadRequest, err.Error()) default: + h.logger.Error("GetAvatar: internal server error", zap.Error(err)) utils.RespondWithError(w, http.StatusInternalServerError, "Failed to serve avatar") } return } // Устанавливаем заголовки + h.logger.Debug("GetAvatar: setting response headers", + zap.String("content_type", contentType), + ) 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", + + h.logger.Info("GetAvatar: avatar served successfully", zap.String("filename", filename), zap.String("content_type", contentType), )