new file: begushiybashkir/bbvue/src/components/AvatarUpload.vue

modified:   begushiybashkir/bbvue/src/stores/auth.js
	modified:   begushiybashkir/bbvue/src/views/Profile.vue
	modified:   begushiybashkir/bbvue/src/views/ProfileEdit.vue
	modified:   serv_nginx/api_bb/go.mod
	modified:   serv_nginx/api_bb/go.sum
	modified:   serv_nginx/api_bb/internal/handlers/auth.go
	new file:   serv_nginx/api_bb/internal/handlers/avatar.go
	modified:   serv_nginx/api_bb/internal/handlers/news_handler.go
	modified:   serv_nginx/api_bb/internal/handlers/user.go
	modified:   serv_nginx/api_bb/internal/models/user.go
	modified:   serv_nginx/api_bb/internal/repository/user_repository.go
	modified:   serv_nginx/api_bb/internal/routes/routes.go
	modified:   serv_nginx/api_bb/internal/service/auth_service.go
	new file:   serv_nginx/api_bb/internal/service/avatar_service.go
	modified:   serv_nginx/api_bb/internal/service/news_service.go
	modified:   serv_nginx/api_bb/internal/service/user_service.go
	modified:   serv_nginx/api_bb/pkg/logger/interface.go
	new file:   serv_nginx/api_bb/pkg/logger/route_logger.go
add structure fix, page, path, routes, component, authStore
for upload, renew and delete avatar
This commit is contained in:
2025-10-13 00:51:13 +05:00
parent 6bb475acb2
commit e13545c5f1
19 changed files with 1269 additions and 580 deletions
+69 -71
View File
@@ -21,7 +21,7 @@ import (
type AuthHandler struct {
authService service.AuthService
jwtService service.JWTService
logger logger.Interface
logger logger.LoggerInterface
}
func NewAuthHandler(authService service.AuthService, jwtService service.JWTService) *AuthHandler {
@@ -165,87 +165,86 @@ func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
}
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
h.logger.Info("handling login request",
zap.String("method", r.Method),
zap.String("path", r.URL.Path),
zap.String("remote_addr", r.RemoteAddr),
)
h.logger.Info("handling login request",
zap.String("method", r.Method),
zap.String("path", r.URL.Path),
zap.String("remote_addr", r.RemoteAddr),
)
// Проверяем Content-Type
if r.Header.Get("Content-Type") != "application/json" {
h.logger.Warn("invalid content type", zap.String("content_type", r.Header.Get("Content-Type")))
utils.RespondWithError(w, http.StatusBadRequest, "Content-Type must be application/json")
return
}
// Проверяем Content-Type
if r.Header.Get("Content-Type") != "application/json" {
h.logger.Warn("invalid content type", zap.String("content_type", r.Header.Get("Content-Type")))
utils.RespondWithError(w, http.StatusBadRequest, "Content-Type must be application/json")
return
}
// Читаем и логируем тело запроса
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
h.logger.Error("failed to read request body", zap.Error(err))
utils.RespondWithError(w, http.StatusBadRequest, "Failed to read request body")
return
}
defer r.Body.Close()
// Читаем и логируем тело запроса
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
h.logger.Error("failed to read request body", zap.Error(err))
utils.RespondWithError(w, http.StatusBadRequest, "Failed to read request body")
return
}
defer r.Body.Close()
// Восстанавливаем тело
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
// Восстанавливаем тело
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
h.logger.Debug("request body", zap.String("body", string(bodyBytes)))
h.logger.Debug("request body", zap.String("body", string(bodyBytes)))
var req LoginRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
h.logger.Error("JSON decode failed",
zap.Error(err),
zap.String("raw_body", string(bodyBytes)),
)
utils.RespondWithError(w, http.StatusBadRequest, "Invalid JSON: "+err.Error())
return
}
var req LoginRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
h.logger.Error("JSON decode failed",
zap.Error(err),
zap.String("raw_body", string(bodyBytes)),
)
utils.RespondWithError(w, http.StatusBadRequest, "Invalid JSON: "+err.Error())
return
}
req.Email = strings.TrimSpace(req.Email)
req.Password = strings.TrimSpace(req.Password)
req.Email = strings.TrimSpace(req.Email)
req.Password = strings.TrimSpace(req.Password)
// Валидация
if req.Email == "" || req.Password == "" {
h.logger.Warn("validation failed",
zap.String("email", req.Email),
zap.Int("password_len", len(req.Password)),
)
utils.RespondWithError(w, http.StatusBadRequest, "Email and password are required")
return
}
// Валидация
if req.Email == "" || req.Password == "" {
h.logger.Warn("validation failed",
zap.String("email", req.Email),
zap.Int("password_len", len(req.Password)),
)
utils.RespondWithError(w, http.StatusBadRequest, "Email and password are required")
return
}
h.logger.Info("attempting login", zap.String("email", req.Email))
h.logger.Info("attempting login", zap.String("email", req.Email))
user, token, err := h.authService.Login(req.Email, req.Password)
if err != nil {
h.logger.Warn("login failed", zap.String("email", req.Email), zap.Error(err))
utils.RespondWithError(w, http.StatusUnauthorized, err.Error())
return
}
user, token, err := h.authService.Login(req.Email, req.Password)
if err != nil {
h.logger.Warn("login failed", zap.String("email", req.Email), zap.Error(err))
utils.RespondWithError(w, http.StatusUnauthorized, err.Error())
return
}
// Устанавливаем куки
http.SetCookie(w, &http.Cookie{
Name: "auth_token",
Value: token,
Path: "/",
HttpOnly: true,
Secure: false,
SameSite: http.SameSiteLaxMode,
Expires: time.Now().Add(24 * time.Hour),
})
// Устанавливаем куки
http.SetCookie(w, &http.Cookie{
Name: "auth_token",
Value: token,
Path: "/",
HttpOnly: true,
Secure: false,
SameSite: http.SameSiteLaxMode,
Expires: time.Now().Add(24 * time.Hour),
})
h.logger.Info("login successful",
zap.Uint("user_id", user.ID),
zap.String("email", user.Email),
)
h.logger.Info("login successful",
zap.Uint("user_id", user.ID),
zap.String("email", user.Email),
)
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
"message": "Login successful",
"token": token,
"user": toUserResponse(user),
})
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
"message": "Login successful",
"token": token,
"user": toUserResponse(user),
})
}
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
@@ -277,4 +276,3 @@ func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
"message": "Logout successful",
})
}
@@ -0,0 +1,97 @@
// handlers/avatar.go
package handlers
import (
"net/http"
"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) Routes() chi.Router {
r := chi.NewRouter()
r.Post("/upload", h.UploadAvatar)
r.Delete("/delete", h.DeleteAvatar)
return r
}
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
}
// Парсим multipart форму
if err := r.ParseMultipartForm(10 << 20); err != nil { // 10MB limit
utils.RespondWithError(w, http.StatusBadRequest, "Failed to parse form: "+err.Error())
return
}
file, header, err := r.FormFile("avatar")
if err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Failed to get file: "+err.Error())
return
}
defer file.Close()
// Проверяем тип файла
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
}
// Загружаем аватар
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
}
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
"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
}
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
}
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
"message": "Avatar deleted successfully",
})
}
@@ -14,265 +14,265 @@ import (
)
type NewsHandler struct {
newsService service.NewsService
logger logger.Interface
validator *validator.Validate
newsService service.NewsService
logger logger.LoggerInterface
validator *validator.Validate
}
func NewNewsHandler(newsService service.NewsService, log logger.Interface) *NewsHandler {
return &NewsHandler{
newsService: newsService,
logger: log,
validator: validator.New(),
}
func NewNewsHandler(newsService service.NewsService, log logger.LoggerInterface) *NewsHandler {
return &NewsHandler{
newsService: newsService,
logger: log,
validator: validator.New(),
}
}
// GetNews возвращает список новостей с пагинацией и фильтрацией
func (h *NewsHandler) GetNews(w http.ResponseWriter, r *http.Request) {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
category := r.URL.Query().Get("category")
if limit == 0 {
limit = 10
}
if limit > 50 {
limit = 50
}
news, total, err := h.newsService.GetAllNews(limit, offset, category)
if err != nil {
h.logger.Error("Failed to get news", zap.Error(err))
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get news")
return
}
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
"news": news,
"total": total,
"limit": limit,
"offset": offset,
})
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
category := r.URL.Query().Get("category")
if limit == 0 {
limit = 10
}
if limit > 50 {
limit = 50
}
news, total, err := h.newsService.GetAllNews(limit, offset, category)
if err != nil {
h.logger.Error("Failed to get news", zap.Error(err))
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get news")
return
}
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
"news": news,
"total": total,
"limit": limit,
"offset": offset,
})
}
// GetNewsByID возвращает конкретную новость
func (h *NewsHandler) GetNewsByID(w http.ResponseWriter, r *http.Request) {
idStr := chi.URLParam(r, "id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid news ID")
return
}
news, err := h.newsService.GetNewsByID(uint(id))
if err != nil {
utils.RespondWithError(w, http.StatusNotFound, "News not found")
return
}
utils.RespondWithJSON(w, http.StatusOK, news)
idStr := chi.URLParam(r, "id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid news ID")
return
}
news, err := h.newsService.GetNewsByID(uint(id))
if err != nil {
utils.RespondWithError(w, http.StatusNotFound, "News not found")
return
}
utils.RespondWithJSON(w, http.StatusOK, news)
}
// CreateNews создает новую новость
func (h *NewsHandler) CreateNews(w http.ResponseWriter, r *http.Request) {
userID, ok := r.Context().Value("userID").(uint)
if !ok {
utils.RespondWithError(w, http.StatusUnauthorized, "Unauthorized")
return
}
var req models.CreateNewsRequest
if err := utils.DecodeJSONBody(w, r, &req); err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid request body")
return
}
if err := h.validator.Struct(req); err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Validation failed: "+err.Error())
return
}
news, err := h.newsService.CreateNews(req, userID)
if err != nil {
h.logger.Error("Failed to create news", zap.Error(err))
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to create news")
return
}
utils.RespondWithJSON(w, http.StatusCreated, news)
userID, ok := r.Context().Value("userID").(uint)
if !ok {
utils.RespondWithError(w, http.StatusUnauthorized, "Unauthorized")
return
}
var req models.CreateNewsRequest
if err := utils.DecodeJSONBody(w, r, &req); err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid request body")
return
}
if err := h.validator.Struct(req); err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Validation failed: "+err.Error())
return
}
news, err := h.newsService.CreateNews(req, userID)
if err != nil {
h.logger.Error("Failed to create news", zap.Error(err))
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to create news")
return
}
utils.RespondWithJSON(w, http.StatusCreated, news)
}
// UpdateNews обновляет новость
func (h *NewsHandler) UpdateNews(w http.ResponseWriter, r *http.Request) {
userID, ok := r.Context().Value("userID").(uint)
if !ok {
utils.RespondWithError(w, http.StatusUnauthorized, "Unauthorized")
return
}
idStr := chi.URLParam(r, "id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid news ID")
return
}
var req models.UpdateNewsRequest
if err := utils.DecodeJSONBody(w, r, &req); err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid request body")
return
}
if err := h.validator.Struct(req); err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Validation failed: "+err.Error())
return
}
news, err := h.newsService.UpdateNews(uint(id), req, userID)
if err != nil {
if err.Error() == "access denied" {
utils.RespondWithError(w, http.StatusForbidden, "Access denied")
return
}
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to update news")
return
}
utils.RespondWithJSON(w, http.StatusOK, news)
userID, ok := r.Context().Value("userID").(uint)
if !ok {
utils.RespondWithError(w, http.StatusUnauthorized, "Unauthorized")
return
}
idStr := chi.URLParam(r, "id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid news ID")
return
}
var req models.UpdateNewsRequest
if err := utils.DecodeJSONBody(w, r, &req); err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid request body")
return
}
if err := h.validator.Struct(req); err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Validation failed: "+err.Error())
return
}
news, err := h.newsService.UpdateNews(uint(id), req, userID)
if err != nil {
if err.Error() == "access denied" {
utils.RespondWithError(w, http.StatusForbidden, "Access denied")
return
}
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to update news")
return
}
utils.RespondWithJSON(w, http.StatusOK, news)
}
// DeleteNews удаляет новость
func (h *NewsHandler) DeleteNews(w http.ResponseWriter, r *http.Request) {
userID, ok := r.Context().Value("userID").(uint)
if !ok {
utils.RespondWithError(w, http.StatusUnauthorized, "Unauthorized")
return
}
idStr := chi.URLParam(r, "id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid news ID")
return
}
err = h.newsService.DeleteNews(uint(id), userID)
if err != nil {
if err.Error() == "access denied" {
utils.RespondWithError(w, http.StatusForbidden, "Access denied")
return
}
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to delete news")
return
}
utils.RespondWithJSON(w, http.StatusOK, map[string]string{"message": "News deleted successfully"})
userID, ok := r.Context().Value("userID").(uint)
if !ok {
utils.RespondWithError(w, http.StatusUnauthorized, "Unauthorized")
return
}
idStr := chi.URLParam(r, "id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid news ID")
return
}
err = h.newsService.DeleteNews(uint(id), userID)
if err != nil {
if err.Error() == "access denied" {
utils.RespondWithError(w, http.StatusForbidden, "Access denied")
return
}
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to delete news")
return
}
utils.RespondWithJSON(w, http.StatusOK, map[string]string{"message": "News deleted successfully"})
}
// CreateComment создает комментарий к новости
func (h *NewsHandler) CreateComment(w http.ResponseWriter, r *http.Request) {
userID, ok := r.Context().Value("userID").(uint)
if !ok {
utils.RespondWithError(w, http.StatusUnauthorized, "Unauthorized")
return
}
newsIDStr := chi.URLParam(r, "id")
newsID, err := strconv.ParseUint(newsIDStr, 10, 32)
if err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid news ID")
return
}
var req models.CreateCommentRequest
if err := utils.DecodeJSONBody(w, r, &req); err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid request body")
return
}
if err := h.validator.Struct(req); err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Validation failed: "+err.Error())
return
}
comment, err := h.newsService.CreateComment(uint(newsID), req, userID)
if err != nil {
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to create comment")
return
}
utils.RespondWithJSON(w, http.StatusCreated, comment)
userID, ok := r.Context().Value("userID").(uint)
if !ok {
utils.RespondWithError(w, http.StatusUnauthorized, "Unauthorized")
return
}
newsIDStr := chi.URLParam(r, "id")
newsID, err := strconv.ParseUint(newsIDStr, 10, 32)
if err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid news ID")
return
}
var req models.CreateCommentRequest
if err := utils.DecodeJSONBody(w, r, &req); err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid request body")
return
}
if err := h.validator.Struct(req); err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Validation failed: "+err.Error())
return
}
comment, err := h.newsService.CreateComment(uint(newsID), req, userID)
if err != nil {
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to create comment")
return
}
utils.RespondWithJSON(w, http.StatusCreated, comment)
}
// GetComments возвращает комментарии к новости
func (h *NewsHandler) GetComments(w http.ResponseWriter, r *http.Request) {
newsIDStr := chi.URLParam(r, "id")
newsID, err := strconv.ParseUint(newsIDStr, 10, 32)
if err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid news ID")
return
}
comments, err := h.newsService.GetCommentsByNewsID(uint(newsID))
if err != nil {
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get comments")
return
}
utils.RespondWithJSON(w, http.StatusOK, comments)
newsIDStr := chi.URLParam(r, "id")
newsID, err := strconv.ParseUint(newsIDStr, 10, 32)
if err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid news ID")
return
}
comments, err := h.newsService.GetCommentsByNewsID(uint(newsID))
if err != nil {
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get comments")
return
}
utils.RespondWithJSON(w, http.StatusOK, comments)
}
// DeleteComment удаляет комментарий
func (h *NewsHandler) DeleteComment(w http.ResponseWriter, r *http.Request) {
userID, ok := r.Context().Value("userID").(uint)
if !ok {
utils.RespondWithError(w, http.StatusUnauthorized, "Unauthorized")
return
}
commentIDStr := chi.URLParam(r, "commentId")
commentID, err := strconv.ParseUint(commentIDStr, 10, 32)
if err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid comment ID")
return
}
err = h.newsService.DeleteComment(uint(commentID), userID)
if err != nil {
if err.Error() == "access denied" {
utils.RespondWithError(w, http.StatusForbidden, "Access denied")
return
}
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to delete comment")
return
}
utils.RespondWithJSON(w, http.StatusOK, map[string]string{"message": "Comment deleted successfully"})
userID, ok := r.Context().Value("userID").(uint)
if !ok {
utils.RespondWithError(w, http.StatusUnauthorized, "Unauthorized")
return
}
commentIDStr := chi.URLParam(r, "commentId")
commentID, err := strconv.ParseUint(commentIDStr, 10, 32)
if err != nil {
utils.RespondWithError(w, http.StatusBadRequest, "Invalid comment ID")
return
}
err = h.newsService.DeleteComment(uint(commentID), userID)
if err != nil {
if err.Error() == "access denied" {
utils.RespondWithError(w, http.StatusForbidden, "Access denied")
return
}
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to delete comment")
return
}
utils.RespondWithJSON(w, http.StatusOK, map[string]string{"message": "Comment deleted successfully"})
}
// GetUserNews возвращает новости конкретного пользователя
func (h *NewsHandler) GetUserNews(w http.ResponseWriter, r *http.Request) {
userID, ok := r.Context().Value("userID").(uint)
if !ok {
utils.RespondWithError(w, http.StatusUnauthorized, "Unauthorized")
return
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
if limit == 0 {
limit = 10
}
news, total, err := h.newsService.GetUserNews(userID, limit, offset)
if err != nil {
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get user news")
return
}
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
"news": news,
"total": total,
})
}
userID, ok := r.Context().Value("userID").(uint)
if !ok {
utils.RespondWithError(w, http.StatusUnauthorized, "Unauthorized")
return
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
if limit == 0 {
limit = 10
}
news, total, err := h.newsService.GetUserNews(userID, limit, offset)
if err != nil {
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get user news")
return
}
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
"news": news,
"total": total,
})
}
+1 -1
View File
@@ -19,7 +19,7 @@ import (
)
type UserHandler struct {
logger logger.Interface
logger logger.LoggerInterface
userService service.UserService
}