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
239 lines
7.3 KiB
Go
239 lines
7.3 KiB
Go
// handlers/avatar.go
|
|
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"api_bb/internal/service"
|
|
"api_bb/pkg/logger"
|
|
"api_bb/pkg/middleware"
|
|
"api_bb/pkg/utils"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type AvatarHandler struct {
|
|
logger logger.LoggerInterface
|
|
avatarService service.AvatarService
|
|
}
|
|
|
|
func NewAvatarHandler(avatarService service.AvatarService) *AvatarHandler {
|
|
return &AvatarHandler{
|
|
logger: logger.NewWrapper(logger.Get().With(zap.String("handler", "avatar"))),
|
|
avatarService: avatarService,
|
|
}
|
|
}
|
|
|
|
func (h *AvatarHandler) UploadAvatar(w http.ResponseWriter, r *http.Request) {
|
|
h.logger.Debug("UploadAvatar START",
|
|
zap.String("method", r.Method),
|
|
zap.String("path", r.URL.Path),
|
|
zap.String("remote_addr", r.RemoteAddr),
|
|
)
|
|
|
|
defer func() {
|
|
h.logger.Debug("UploadAvatar END",
|
|
zap.String("method", r.Method),
|
|
zap.String("path", r.URL.Path),
|
|
)
|
|
}()
|
|
|
|
user, ok := middleware.GetUserFromContext(r.Context())
|
|
if !ok {
|
|
h.logger.Warn("UploadAvatar: authentication required")
|
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
|
return
|
|
}
|
|
|
|
h.logger.Debug("UploadAvatar: user authenticated",
|
|
zap.Int64("user_id", int64(user.ID)),
|
|
zap.String("username", user.FirstName+user.LastName),
|
|
)
|
|
|
|
// Парсим 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
|
|
}
|
|
|
|
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) {
|
|
h.logger.Debug("DeleteAvatar START",
|
|
zap.String("method", r.Method),
|
|
zap.String("path", r.URL.Path),
|
|
zap.String("remote_addr", r.RemoteAddr),
|
|
)
|
|
|
|
defer func() {
|
|
h.logger.Debug("DeleteAvatar END",
|
|
zap.String("method", r.Method),
|
|
zap.String("path", r.URL.Path),
|
|
)
|
|
}()
|
|
|
|
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/avatars/{filename}
|
|
func (h *AvatarHandler) GetAvatar(w http.ResponseWriter, r *http.Request) {
|
|
filename := chi.URLParam(r, "filename")
|
|
|
|
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),
|
|
)
|
|
}()
|
|
|
|
// Валидация имени файла
|
|
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),
|
|
)
|
|
|
|
// Используем 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("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("GetAvatar: avatar served successfully",
|
|
zap.String("filename", filename),
|
|
zap.String("content_type", contentType),
|
|
)
|
|
} |