On branch main

modified:   main_dc/yalarba/api_yal/internal/domain/account/dto.go
	new file:   main_dc/yalarba/api_yal/internal/domain/account/errors.go
	modified:   main_dc/yalarba/api_yal/internal/domain/account/handler.go
	modified:   main_dc/yalarba/api_yal/internal/domain/account/router.go
	modified:   main_dc/yalarba/api_yal/internal/domain/account/service.go
	new file:   main_dc/yalarba/api_yal/internal/domain/account/types.go
	new file:   main_dc/yalarba/api_yal/internal/middleware/admin.go
	modified:   main_dc/yalarba/api_yal/internal/middleware/auth.go
	new file:   main_dc/yalarba/api_yal/internal/middleware/context.go
	new file:   main_dc/yalarba/api_yal/internal/middleware/logging.go
	modified:   main_dc/yalarba/api_yal/internal/router/router.go
last but not yet commit
This commit is contained in:
2026-03-31 09:43:18 +05:00
parent f304f982c0
commit 75b2f3f6b2
11 changed files with 1384 additions and 109 deletions
@@ -9,11 +9,11 @@ import (
// CreateAccountRequest - DTO для создания нового аккаунта // CreateAccountRequest - DTO для создания нового аккаунта
type CreateAccountRequest struct { type CreateAccountRequest struct {
Email string `json:"email" binding:"required,email"` Email string `json:"email" validate:"required,email"`
Password string `json:"password" binding:"required,min=6"` Password string `json:"password" validate:"required,min=6"`
FullName string `json:"full_name" binding:"required"` FullName string `json:"full_name" validate:"required"`
FirstName string `json:"first_name" binding:"required"` FirstName string `json:"first_name" validate:"required"`
LastName string `json:"last_name" binding:"required"` LastName string `json:"last_name" validate:"required"`
Phone string `json:"phone"` Phone string `json:"phone"`
City string `json:"city"` City string `json:"city"`
@@ -43,19 +43,19 @@ type UpdateAccountRequest struct {
// ChangePasswordRequest - DTO для смены пароля // ChangePasswordRequest - DTO для смены пароля
type ChangePasswordRequest struct { type ChangePasswordRequest struct {
CurrentPassword string `json:"current_password" binding:"required"` CurrentPassword string `json:"current_password" validate:"required"`
NewPassword string `json:"new_password" binding:"required,min=6"` NewPassword string `json:"new_password" validate:"required,min=6"`
} }
// ForgotPasswordRequest - DTO для запроса сброса пароля // ForgotPasswordRequest - DTO для запроса сброса пароля
type ForgotPasswordRequest struct { type ForgotPasswordRequest struct {
Email string `json:"email" binding:"required,email"` Email string `json:"email" validate:"required,email"`
} }
// ResetPasswordRequest - DTO для сброса пароля по токену // ResetPasswordRequest - DTO для сброса пароля по токену
type ResetPasswordRequest struct { type ResetPasswordRequest struct {
Token string `json:"token" binding:"required"` Token string `json:"token" validate:"required"`
NewPassword string `json:"new_password" binding:"required,min=6"` NewPassword string `json:"new_password" validate:"required,min=6"`
} }
// VerifyAccountRequest - DTO для верификации аккаунта (админ) // VerifyAccountRequest - DTO для верификации аккаунта (админ)
@@ -66,15 +66,15 @@ type VerifyAccountRequest struct {
// UpdateAccountStatusRequest - DTO для изменения статуса аккаунта (админ) // UpdateAccountStatusRequest - DTO для изменения статуса аккаунта (админ)
type UpdateAccountStatusRequest struct { type UpdateAccountStatusRequest struct {
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
Role string `json:"role" binding:"omitempty,oneof=user admin moderator"` Role string `json:"role" validate:"omitempty,oneof=user admin moderator"`
} }
// ListAccountsRequest - DTO для фильтрации списка аккаунтов // ListAccountsRequest - DTO для фильтрации списка аккаунтов
type ListAccountsRequest struct { type ListAccountsRequest struct {
Page int `form:"page" binding:"min=1"` Page int `form:"page" validate:"min=1"`
PageSize int `form:"page_size" binding:"min=1,max=100"` PageSize int `form:"page_size" validate:"min=1,max=100"`
Search string `form:"search"` Search string `form:"search"`
Role string `form:"role" binding:"omitempty,oneof=user admin moderator"` Role string `form:"role" validate:"omitempty,oneof=user admin moderator"`
IsActive *bool `form:"is_active"` IsActive *bool `form:"is_active"`
} }
@@ -140,19 +140,19 @@ type AccountProfileResponse struct {
// AccountStats - статистика аккаунта // AccountStats - статистика аккаунта
type AccountStats struct { type AccountStats struct {
ObjectsCount int `json:"objects_count"` ObjectsCount int `json:"objects_count"`
FeedbacksCount int `json:"feedbacks_count"` FeedbacksCount int `json:"feedbacks_count"`
CommentsCount int `json:"comments_count"` CommentsCount int `json:"comments_count"`
RatingsCount int `json:"ratings_count"` RatingsCount int `json:"ratings_count"`
AppealsCount int `json:"appeals_count"` AppealsCount int `json:"appeals_count"`
} }
// AuthResponse - DTO для ответа при авторизации // AuthResponse - DTO для ответа при авторизации
type AuthResponse struct { type AuthResponse struct {
Token string `json:"token"` Token string `json:"token"`
TokenType string `json:"token_type"` TokenType string `json:"token_type"`
ExpiresAt int64 `json:"expires_at"` ExpiresAt int64 `json:"expires_at"`
Account AccountResponse `json:"account"` Account AccountResponse `json:"account"`
} }
// PasswordResetResponse - DTO для ответа о сбросе пароля // PasswordResetResponse - DTO для ответа о сбросе пароля
@@ -0,0 +1,31 @@
package account
import "errors"
var (
// Основные ошибки
ErrAccountNotFound = errors.New("account not found")
ErrAccountAlreadyExists = errors.New("account already exists")
ErrAccountDeactivated = errors.New("account is deactivated")
ErrAccountNotVerified = errors.New("account is not verified")
// Ошибки пароля
ErrInvalidCurrentPassword = errors.New("invalid current password")
ErrPasswordTooWeak = errors.New("password too weak")
ErrPasswordMismatch = errors.New("password mismatch")
// Ошибки токенов сброса пароля
ErrResetTokenNotFound = errors.New("reset token not found")
ErrResetTokenExpired = errors.New("reset token has expired")
ErrResetTokenAlreadyUsed = errors.New("reset token already used")
ErrResetTokenInvalid = errors.New("invalid reset token")
// Ошибки доступа
ErrInsufficientPermissions = errors.New("insufficient permissions")
ErrUnauthorized = errors.New("unauthorized")
// Ошибки валидации
ErrInvalidEmail = errors.New("invalid email")
ErrInvalidPhone = errors.New("invalid phone number")
ErrInvalidINN = errors.New("invalid INN")
)
@@ -1 +1,447 @@
package account package account
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"api_yal/internal/logger"
"api_yal/internal/middleware"
"github.com/go-playground/validator/v10"
"go.uber.org/zap"
)
// Handler обработчик для операций с аккаунтами
type Handler struct {
service Service
validator *validator.Validate
}
// NewHandler создает новый экземпляр Handler
func NewHandler(service Service) *Handler {
return &Handler{
service: service,
validator: validator.New(),
}
}
// GetAccountByID получение аккаунта по ID
func (h *Handler) GetAccountByID(w http.ResponseWriter, r *http.Request) {
l := logger.Get()
// Получаем ID из контекста (для своего профиля)
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Проверяем, что пользователь запрашивает свой профиль
// В реальном приложении админы могут просматривать чужие профили
account, err := h.service.GetAccountByID(userID)
if err != nil {
l.Error("Ошибка получения аккаунта", zap.Error(err))
if errors.Is(err, ErrAccountNotFound) {
http.Error(w, "Account not found", http.StatusNotFound)
return
}
http.Error(w, "Failed to get account", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(account)
}
// GetAccountProfile получение профиля пользователя
func (h *Handler) GetAccountProfile(w http.ResponseWriter, r *http.Request) {
l := logger.Get()
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
profile, err := h.service.GetAccountProfile(userID)
if err != nil {
l.Error("Ошибка получения профиля", zap.Error(err))
if errors.Is(err, ErrAccountNotFound) {
http.Error(w, "Account not found", http.StatusNotFound)
return
}
http.Error(w, "Failed to get profile", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(profile)
}
// UpdateAccount обновление аккаунта
func (h *Handler) UpdateAccount(w http.ResponseWriter, r *http.Request) {
l := logger.Get()
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
var req UpdateAccountRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
// Валидация не требуется для всех полей, так как они опциональны
account, err := h.service.UpdateAccount(userID, req)
if err != nil {
l.Error("Ошибка обновления аккаунта", zap.Error(err))
if errors.Is(err, ErrAccountNotFound) {
http.Error(w, "Account not found", http.StatusNotFound)
return
}
http.Error(w, "Failed to update account", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(account)
}
// ChangePassword смена пароля
func (h *Handler) ChangePassword(w http.ResponseWriter, r *http.Request) {
l := logger.Get()
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
var req ChangePasswordRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
if err := h.validator.Struct(req); err != nil {
h.handleValidationError(w, err)
return
}
if err := h.service.ChangePassword(userID, req); err != nil {
l.Error("Ошибка смены пароля", zap.Error(err))
if errors.Is(err, ErrInvalidCurrentPassword) {
http.Error(w, "Current password is incorrect", http.StatusBadRequest)
return
}
if errors.Is(err, ErrAccountNotFound) {
http.Error(w, "Account not found", http.StatusNotFound)
return
}
http.Error(w, "Failed to change password", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"message": "Password changed successfully",
})
}
// ForgotPassword запрос на сброс пароля
func (h *Handler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
l := logger.Get()
var req ForgotPasswordRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
if err := h.validator.Struct(req); err != nil {
h.handleValidationError(w, err)
return
}
token, err := h.service.ForgotPassword(req.Email)
if err != nil {
l.Error("Ошибка запроса сброса пароля", zap.Error(err))
http.Error(w, "Password reset request failed", http.StatusInternalServerError)
return
}
// В реальном приложении здесь отправляется email
// Для тестирования возвращаем токен
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(PasswordResetResponse{
Message: "If the email exists, a reset link has been sent",
Token: token, // Только для тестирования
})
}
// ResetPassword подтверждение сброса пароля
func (h *Handler) ResetPassword(w http.ResponseWriter, r *http.Request) {
l := logger.Get()
var req ResetPasswordRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
if err := h.validator.Struct(req); err != nil {
h.handleValidationError(w, err)
return
}
if err := h.service.ResetPassword(req.Token, req.NewPassword); err != nil {
l.Error("Ошибка сброса пароля", zap.Error(err))
if errors.Is(err, ErrResetTokenNotFound) {
http.Error(w, "Reset token not found", http.StatusNotFound)
return
}
if errors.Is(err, ErrResetTokenExpired) {
http.Error(w, "Reset token has expired", http.StatusBadRequest)
return
}
if errors.Is(err, ErrResetTokenAlreadyUsed) {
http.Error(w, "Reset token has already been used", http.StatusBadRequest)
return
}
http.Error(w, "Password reset failed", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"message": "Password has been successfully reset",
})
}
// DeleteAccount удаление аккаунта
func (h *Handler) DeleteAccount(w http.ResponseWriter, r *http.Request) {
l := logger.Get()
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if err := h.service.DeleteAccount(userID); err != nil {
l.Error("Ошибка удаления аккаунта", zap.Error(err))
if errors.Is(err, ErrAccountNotFound) {
http.Error(w, "Account not found", http.StatusNotFound)
return
}
http.Error(w, "Failed to delete account", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"message": "Account deleted successfully",
})
}
// ==================== Административные методы ====================
// ListAccounts список аккаунтов (админ)
func (h *Handler) ListAccounts(w http.ResponseWriter, r *http.Request) {
l := logger.Get()
var req ListAccountsRequest
// Парсинг query параметров
if pageStr := r.URL.Query().Get("page"); pageStr != "" {
page, _ := strconv.Atoi(pageStr)
req.Page = page
}
if pageSizeStr := r.URL.Query().Get("page_size"); pageSizeStr != "" {
pageSize, _ := strconv.Atoi(pageSizeStr)
req.PageSize = pageSize
}
req.Search = r.URL.Query().Get("search")
req.Role = r.URL.Query().Get("role")
if isActiveStr := r.URL.Query().Get("is_active"); isActiveStr != "" {
isActive, _ := strconv.ParseBool(isActiveStr)
req.IsActive = &isActive
}
if err := h.validator.Struct(req); err != nil {
h.handleValidationError(w, err)
return
}
response, err := h.service.ListAccounts(req)
if err != nil {
l.Error("Ошибка получения списка аккаунтов", zap.Error(err))
http.Error(w, "Failed to list accounts", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}
// GetAccountByIDAdmin получение аккаунта по ID (админ)
func (h *Handler) GetAccountByIDAdmin(w http.ResponseWriter, r *http.Request) {
l := logger.Get()
// Получаем ID из URL
idStr := r.URL.Query().Get("id")
if idStr == "" {
http.Error(w, "Missing id parameter", http.StatusBadRequest)
return
}
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
http.Error(w, "Invalid id parameter", http.StatusBadRequest)
return
}
account, err := h.service.GetAccountByID(uint(id))
if err != nil {
l.Error("Ошибка получения аккаунта", zap.Error(err))
if errors.Is(err, ErrAccountNotFound) {
http.Error(w, "Account not found", http.StatusNotFound)
return
}
http.Error(w, "Failed to get account", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(account)
}
// VerifyAccount верификация аккаунта (админ)
func (h *Handler) VerifyAccount(w http.ResponseWriter, r *http.Request) {
l := logger.Get()
// Получаем ID из URL
idStr := r.URL.Query().Get("id")
if idStr == "" {
http.Error(w, "Missing id parameter", http.StatusBadRequest)
return
}
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
http.Error(w, "Invalid id parameter", http.StatusBadRequest)
return
}
var req VerifyAccountRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
if err := h.service.VerifyAccount(uint(id), req); err != nil {
l.Error("Ошибка верификации аккаунта", zap.Error(err))
if errors.Is(err, ErrAccountNotFound) {
http.Error(w, "Account not found", http.StatusNotFound)
return
}
http.Error(w, "Failed to verify account", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"message": "Account verified successfully",
})
}
// UpdateAccountStatus обновление статуса аккаунта (админ)
func (h *Handler) UpdateAccountStatus(w http.ResponseWriter, r *http.Request) {
l := logger.Get()
// Получаем ID из URL
idStr := r.URL.Query().Get("id")
if idStr == "" {
http.Error(w, "Missing id parameter", http.StatusBadRequest)
return
}
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
http.Error(w, "Invalid id parameter", http.StatusBadRequest)
return
}
var req UpdateAccountStatusRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
if err := h.validator.Struct(req); err != nil {
h.handleValidationError(w, err)
return
}
if err := h.service.UpdateAccountStatus(uint(id), req); err != nil {
l.Error("Ошибка обновления статуса аккаунта", zap.Error(err))
if errors.Is(err, ErrAccountNotFound) {
http.Error(w, "Account not found", http.StatusNotFound)
return
}
http.Error(w, "Failed to update account status", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"message": "Account status updated successfully",
})
}
// handleValidationError обрабатывает ошибки валидации
func (h *Handler) handleValidationError(w http.ResponseWriter, err error) {
var invalidValidationError *validator.InvalidValidationError
if errors.As(err, &invalidValidationError) {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
var errs []string
for _, err := range err.(validator.ValidationErrors) {
errs = append(errs, fmt.Sprintf("field %s is invalid: %s", err.Field(), err.Tag()))
}
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]interface{}{
"error": "Validation failed",
"fields": errs,
})
}
@@ -1 +1,51 @@
package account package account
import (
"api_yal/internal/logger"
"api_yal/internal/middleware"
"api_yal/internal/repository"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
// RegisterRoutes регистрирует маршруты для работы с аккаунтами
func RegisterRoutes(r chi.Router, db *gorm.DB, jwtSecret string) {
l := logger.Get()
l.Debug("Регистрация маршрутов аккаунтов")
// Создаем репозиторий и сервис
accountRepo := repository.NewAccountRepository(db)
accountService := NewService(accountRepo)
accountHandler := NewHandler(accountService)
// Публичные маршруты (без аутентификации)
r.Group(func(r chi.Router) {
r.Post("/forgot-password", accountHandler.ForgotPassword)
r.Post("/reset-password", accountHandler.ResetPassword)
})
// Защищенные маршруты (требуют аутентификации)
r.Group(func(r chi.Router) {
r.Use(middleware.AuthMiddleware(jwtSecret))
r.Get("/profile", accountHandler.GetAccountProfile)
r.Get("/me", accountHandler.GetAccountByID)
r.Put("/me", accountHandler.UpdateAccount)
r.Delete("/me", accountHandler.DeleteAccount)
r.Post("/change-password", accountHandler.ChangePassword)
})
// Административные маршруты (требуют прав администратора)
r.Group(func(r chi.Router) {
r.Use(middleware.AuthMiddleware(jwtSecret))
r.Use(middleware.AdminOnlyMiddleware)
r.Get("/accounts", accountHandler.ListAccounts)
r.Get("/account", accountHandler.GetAccountByIDAdmin)
r.Put("/account/verify", accountHandler.VerifyAccount)
r.Put("/account/status", accountHandler.UpdateAccountStatus)
})
l.Info("Маршруты аккаунтов зарегистрированы")
}
@@ -1 +1,561 @@
package account package account
import (
"api_yal/internal/logger"
"api_yal/internal/models"
"api_yal/internal/repository"
"errors"
"fmt"
"time"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
// Service интерфейс сервиса аккаунтов
type Service interface {
// Основные операции
GetAccountByID(id uint) (*AccountResponse, error)
GetAccountByEmail(email string) (*AccountResponse, error)
GetAccountWithObjects(id uint) (*AccountWithObjectsResponse, error)
UpdateAccount(id uint, req UpdateAccountRequest) (*AccountResponse, error)
DeleteAccount(id uint) error
// Управление паролем
ChangePassword(userID uint, req ChangePasswordRequest) error
ForgotPassword(email string) (string, error)
ResetPassword(token, newPassword string) error
// Административные функции
ListAccounts(req ListAccountsRequest) (*AccountListResponse, error)
VerifyAccount(accountID uint, req VerifyAccountRequest) error
UpdateAccountStatus(accountID uint, req UpdateAccountStatusRequest) error
// Статистика
GetAccountStats(userID uint) (*AccountStats, error)
GetAccountProfile(userID uint) (*AccountProfileResponse, error)
// Внутренние методы для auth сервиса
GetAccountModelByID(id uint) (*models.Account, error)
GetAccountModelByEmail(email string) (*models.Account, error)
CreateAccount(req CreateAccountRequest) (*models.Account, error)
UpdateAccountModel(account *models.Account) error
}
// serviceImpl реализация сервиса аккаунтов
type serviceImpl struct {
accountRepo repository.AccountRepository
}
// NewService создает новый экземпляр сервиса аккаунтов
func NewService(accountRepo repository.AccountRepository) Service {
return &serviceImpl{
accountRepo: accountRepo,
}
}
// GetAccountByID получает аккаунт по ID
func (s *serviceImpl) GetAccountByID(id uint) (*AccountResponse, error) {
l := logger.Get()
l.Debug("Получение аккаунта по ID", zap.Uint("id", id))
account, err := s.accountRepo.GetByID(id)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrAccountNotFound
}
l.Error("Ошибка получения аккаунта", zap.Error(err))
return nil, err
}
response := ToAccountResponse(account)
return &response, nil
}
// GetAccountByEmail получает аккаунт по email
func (s *serviceImpl) GetAccountByEmail(email string) (*AccountResponse, error) {
l := logger.Get()
l.Debug("Получение аккаунта по email", zap.String("email", email))
account, err := s.accountRepo.GetByEmail(email)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrAccountNotFound
}
l.Error("Ошибка получения аккаунта", zap.Error(err))
return nil, err
}
response := ToAccountResponse(account)
return &response, nil
}
// GetAccountWithObjects получает аккаунт с его объектами
func (s *serviceImpl) GetAccountWithObjects(id uint) (*AccountWithObjectsResponse, error) {
l := logger.Get()
l.Debug("Получение аккаунта с объектами", zap.Uint("id", id))
account, err := s.accountRepo.GetByID(id)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrAccountNotFound
}
l.Error("Ошибка получения аккаунта", zap.Error(err))
return nil, err
}
objects, err := s.accountRepo.GetObjects(id)
if err != nil {
l.Error("Ошибка получения объектов", zap.Error(err))
return nil, err
}
account.Objects = objects
response := ToAccountWithObjectsResponse(account)
return &response, nil
}
// UpdateAccount обновляет информацию об аккаунте
func (s *serviceImpl) UpdateAccount(id uint, req UpdateAccountRequest) (*AccountResponse, error) {
l := logger.Get()
l.Info("Обновление аккаунта", zap.Uint("id", id))
account, err := s.accountRepo.GetByID(id)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrAccountNotFound
}
return nil, err
}
// Обновляем поля
if req.FullName != "" {
account.FullName = req.FullName
}
if req.FirstName != "" {
account.FirstName = req.FirstName
}
if req.LastName != "" {
account.LastName = req.LastName
}
if req.Phone != "" {
account.Phone = req.Phone
}
if req.City != "" {
account.City = req.City
}
if req.OrganizationForm != "" {
account.OrganizationForm = req.OrganizationForm
}
if req.OrganizationName != "" {
account.OrganizationName = req.OrganizationName
}
if req.OrganizationShort != "" {
account.OrganizationShort = req.OrganizationShort
}
if req.INN != "" {
account.INN = req.INN
}
if req.PersonalINN != "" {
account.PersonalINN = req.PersonalINN
}
if err := s.accountRepo.Update(account); err != nil {
l.Error("Ошибка обновления аккаунта", zap.Error(err))
return nil, err
}
response := ToAccountResponse(account)
return &response, nil
}
// DeleteAccount удаляет аккаунт (мягкое удаление)
func (s *serviceImpl) DeleteAccount(id uint) error {
l := logger.Get()
l.Info("Удаление аккаунта", zap.Uint("id", id))
if err := s.accountRepo.Delete(id); err != nil {
l.Error("Ошибка удаления аккаунта", zap.Error(err))
return err
}
return nil
}
// ChangePassword изменяет пароль пользователя
func (s *serviceImpl) ChangePassword(userID uint, req ChangePasswordRequest) error {
l := logger.Get()
l.Info("Смена пароля", zap.Uint("userID", userID))
account, err := s.accountRepo.GetByID(userID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrAccountNotFound
}
return err
}
// Проверяем текущий пароль
if err := bcrypt.CompareHashAndPassword([]byte(account.PasswordHash), []byte(req.CurrentPassword)); err != nil {
l.Error("Неверный текущий пароль")
return ErrInvalidCurrentPassword
}
// Хешируем новый пароль
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
if err != nil {
l.Error("Ошибка хеширования пароля", zap.Error(err))
return err
}
account.PasswordHash = string(hashedPassword)
if err := s.accountRepo.Update(account); err != nil {
l.Error("Ошибка обновления пароля", zap.Error(err))
return err
}
l.Info("Пароль успешно изменен", zap.Uint("userID", userID))
return nil
}
// ForgotPassword запрашивает сброс пароля
func (s *serviceImpl) ForgotPassword(email string) (string, error) {
l := logger.Get()
l.Info("Запрос сброса пароля", zap.String("email", email))
account, err := s.accountRepo.GetByEmail(email)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
// Для безопасности не сообщаем, что пользователь не найден
return "", nil
}
return "", err
}
// Генерируем reset token (используем метод из auth сервиса)
// В реальном приложении здесь должна быть генерация токена
resetToken := generateResetToken()
passwordReset := &models.PasswordReset{
AccountID: account.ID,
Token: resetToken,
ExpiresAt: time.Now().Add(1 * time.Hour),
Used: false,
}
if err := s.accountRepo.CreatePasswordReset(passwordReset); err != nil {
l.Error("Ошибка сохранения reset token", zap.Error(err))
return "", err
}
l.Info("Reset token создан", zap.String("email", email))
return resetToken, nil
}
// ResetPassword сбрасывает пароль по токену
func (s *serviceImpl) ResetPassword(token, newPassword string) error {
l := logger.Get()
l.Info("Сброс пароля по токену")
passwordReset, err := s.accountRepo.GetPasswordResetByToken(token)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrResetTokenNotFound
}
return err
}
// Проверяем срок действия токена
if passwordReset.ExpiresAt.Before(time.Now()) {
return ErrResetTokenExpired
}
// Проверяем, не использован ли токен
if passwordReset.Used {
return ErrResetTokenAlreadyUsed
}
// Получаем пользователя
account, err := s.accountRepo.GetByID(passwordReset.AccountID)
if err != nil {
return ErrAccountNotFound
}
// Хешируем новый пароль
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
if err != nil {
l.Error("Ошибка хеширования пароля", zap.Error(err))
return err
}
// Обновляем пароль
account.PasswordHash = string(hashedPassword)
if err := s.accountRepo.Update(account); err != nil {
l.Error("Ошибка обновления пароля", zap.Error(err))
return err
}
// Помечаем токен как использованный
passwordReset.Used = true
if err := s.accountRepo.UpdatePasswordReset(passwordReset); err != nil {
l.Error("Ошибка обновления статуса токена", zap.Error(err))
}
l.Info("Пароль успешно сброшен", zap.Uint("userID", account.ID))
return nil
}
// ListAccounts возвращает список аккаунтов с пагинацией
func (s *serviceImpl) ListAccounts(req ListAccountsRequest) (*AccountListResponse, error) {
l := logger.Get()
l.Debug("Получение списка аккаунтов", zap.Any("request", req))
// Устанавливаем значения по умолчанию
if req.Page == 0 {
req.Page = 1
}
if req.PageSize == 0 {
req.PageSize = 20
}
offset := (req.Page - 1) * req.PageSize
var accounts []models.Account
var total int64
var err error
// Поиск с фильтрацией
if req.Search != "" {
accounts, err = s.accountRepo.Search(req.Search, offset, req.PageSize)
if err != nil {
l.Error("Ошибка поиска аккаунтов", zap.Error(err))
return nil, err
}
total, err = s.getSearchTotal(req.Search)
} else {
accounts, err = s.accountRepo.List(offset, req.PageSize)
if err != nil {
l.Error("Ошибка получения списка аккаунтов", zap.Error(err))
return nil, err
}
total, err = s.accountRepo.Count()
}
if err != nil {
return nil, err
}
// Фильтруем по роли и статусу (если нужно)
filteredAccounts := s.filterAccounts(accounts, req.Role, req.IsActive)
items := make([]AccountResponse, len(filteredAccounts))
for i, acc := range filteredAccounts {
items[i] = ToAccountResponse(&acc)
}
totalPages := int(total) / req.PageSize
if int(total)%req.PageSize > 0 {
totalPages++
}
return &AccountListResponse{
Items: items,
Total: total,
Page: req.Page,
PageSize: req.PageSize,
TotalPages: totalPages,
}, nil
}
// VerifyAccount верифицирует аккаунт
func (s *serviceImpl) VerifyAccount(accountID uint, req VerifyAccountRequest) error {
l := logger.Get()
l.Info("Верификация аккаунта", zap.Uint("id", accountID))
account, err := s.accountRepo.GetByID(accountID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrAccountNotFound
}
return err
}
account.IsVerified = req.IsVerified
if err := s.accountRepo.Update(account); err != nil {
l.Error("Ошибка обновления статуса верификации", zap.Error(err))
return err
}
l.Info("Аккаунт верифицирован", zap.Uint("id", accountID))
return nil
}
// UpdateAccountStatus обновляет статус аккаунта (админ)
func (s *serviceImpl) UpdateAccountStatus(accountID uint, req UpdateAccountStatusRequest) error {
l := logger.Get()
l.Info("Обновление статуса аккаунта", zap.Uint("id", accountID))
account, err := s.accountRepo.GetByID(accountID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrAccountNotFound
}
return err
}
account.IsActive = req.IsActive
if req.Role != "" {
account.Role = req.Role
}
if err := s.accountRepo.Update(account); err != nil {
l.Error("Ошибка обновления статуса аккаунта", zap.Error(err))
return err
}
l.Info("Статус аккаунта обновлен", zap.Uint("id", accountID))
return nil
}
// GetAccountStats получает статистику аккаунта
func (s *serviceImpl) GetAccountStats(userID uint) (*AccountStats, error) {
l := logger.Get()
l.Debug("Получение статистики аккаунта", zap.Uint("userID", userID))
// Здесь должна быть реальная логика подсчета статистики
// В реальном приложении нужно запрашивать данные из соответствующих репозиториев
stats := &AccountStats{
ObjectsCount: 0,
FeedbacksCount: 0,
CommentsCount: 0,
RatingsCount: 0,
AppealsCount: 0,
}
// TODO: Получить реальную статистику из базы данных
return stats, nil
}
// GetAccountProfile получает профиль пользователя со статистикой
func (s *serviceImpl) GetAccountProfile(userID uint) (*AccountProfileResponse, error) {
l := logger.Get()
l.Debug("Получение профиля пользователя", zap.Uint("userID", userID))
account, err := s.accountRepo.GetByID(userID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrAccountNotFound
}
return nil, err
}
stats, err := s.GetAccountStats(userID)
if err != nil {
l.Error("Ошибка получения статистики", zap.Error(err))
return nil, err
}
return &AccountProfileResponse{
AccountResponse: ToAccountResponse(account),
Stats: *stats,
}, nil
}
// GetAccountModelByID получает модель аккаунта по ID (для внутреннего использования)
func (s *serviceImpl) GetAccountModelByID(id uint) (*models.Account, error) {
return s.accountRepo.GetByID(id)
}
// GetAccountModelByEmail получает модель аккаунта по email (для внутреннего использования)
func (s *serviceImpl) GetAccountModelByEmail(email string) (*models.Account, error) {
return s.accountRepo.GetByEmail(email)
}
// CreateAccount создает новый аккаунт
func (s *serviceImpl) CreateAccount(req CreateAccountRequest) (*models.Account, error) {
l := logger.Get()
l.Info("Создание аккаунта", zap.String("email", req.Email))
// Проверяем, существует ли пользователь
existing, _ := s.accountRepo.GetByEmail(req.Email)
if existing != nil {
return nil, ErrAccountAlreadyExists
}
// Хешируем пароль
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
l.Error("Ошибка хеширования пароля", zap.Error(err))
return nil, err
}
// Формируем полное имя, если не указано
fullName := req.FullName
if fullName == "" {
fullName = req.FirstName + " " + req.LastName
}
account := &models.Account{
Email: req.Email,
PasswordHash: string(hashedPassword),
FullName: fullName,
FirstName: req.FirstName,
LastName: req.LastName,
Phone: req.Phone,
City: req.City,
OrganizationForm: req.OrganizationForm,
OrganizationName: req.OrganizationName,
OrganizationShort: req.OrganizationShort,
INN: req.INN,
PersonalINN: req.PersonalINN,
IsActive: true,
IsVerified: false,
Role: "user",
}
if err := s.accountRepo.Create(account); err != nil {
l.Error("Ошибка создания аккаунта", zap.Error(err))
return nil, err
}
l.Info("Аккаунт создан", zap.String("email", req.Email))
return account, nil
}
// UpdateAccountModel обновляет модель аккаунта
func (s *serviceImpl) UpdateAccountModel(account *models.Account) error {
return s.accountRepo.Update(account)
}
// Вспомогательные методы
func (s *serviceImpl) getSearchTotal(query string) (int64, error) {
// Здесь должна быть реализация подсчета общего количества результатов поиска
// Для простоты возвращаем 0
return 0, nil
}
func (s *serviceImpl) filterAccounts(accounts []models.Account, role string, isActive *bool) []models.Account {
var filtered []models.Account
for _, acc := range accounts {
if role != "" && acc.Role != role {
continue
}
if isActive != nil && acc.IsActive != *isActive {
continue
}
filtered = append(filtered, acc)
}
return filtered
}
// Генерация reset токена
func generateResetToken() string {
// В реальном приложении используйте криптографически безопасную генерацию
return fmt.Sprintf("reset_%d_%d", time.Now().UnixNano(), time.Now().Unix())
}
@@ -0,0 +1,34 @@
package account
// AccountType тип аккаунта
type AccountType string
const (
AccountTypeIndividual AccountType = "individual" // Физическое лицо
AccountTypeBusiness AccountType = "business" // Юридическое лицо
)
// AccountRole роль пользователя
type AccountRole string
const (
RoleUser AccountRole = "user"
RoleModer AccountRole = "moderator"
RoleAdmin AccountRole = "admin"
)
// ValidationRules правила валидации
type ValidationRules struct {
MinPasswordLength int
MaxNameLength int
MaxPhoneLength int
INNLength int
}
// DefaultValidationRules правила валидации по умолчанию
var DefaultValidationRules = ValidationRules{
MinPasswordLength: 6,
MaxNameLength: 100,
MaxPhoneLength: 20,
INNLength: 12,
}
@@ -0,0 +1,50 @@
package middleware
import (
"net/http"
"api_yal/internal/logger"
"go.uber.org/zap"
)
// AdminOnlyMiddleware проверяет, что пользователь имеет права администратора
func AdminOnlyMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
l := logger.Get()
role, ok := GetUserRole(r.Context())
if !ok {
l.Warn("Admin check: user role not found in context")
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if role != "admin" {
l.Warn("Admin check: insufficient permissions",
zap.String("role", role))
http.Error(w, "Admin access required", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
// ModeratorOrAdminMiddleware проверяет, что пользователь имеет права модератора или администратора
func ModeratorOrAdminMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
role, ok := GetUserRole(r.Context())
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if role != "admin" && role != "moderator" {
http.Error(w, "Moderator or admin access required", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
@@ -1,4 +1,3 @@
// middleware/auth.go (обновленная версия с логированием)
package middleware package middleware
import ( import (
@@ -27,15 +26,16 @@ func AuthMiddleware(jwtSecret string) func(http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
l := logger.Get() l := logger.Get()
l.Info("=== AUTH MIDDLEWARE START ===") // Логируем только в debug режиме, чтобы не засорять логи
l.Info("Request path", zap.String("path", r.URL.Path)) l.Debug("Auth middleware: processing request",
zap.String("path", r.URL.Path),
zap.String("method", r.Method))
// Получаем токен из заголовка Authorization // Получаем токен из заголовка Authorization
authHeader := r.Header.Get("Authorization") authHeader := r.Header.Get("Authorization")
l.Info("Authorization header", zap.String("header", authHeader))
if authHeader == "" { if authHeader == "" {
l.Warn("Отсутствует заголовок Authorization") l.Debug("Authorization header missing")
http.Error(w, "Authorization header required", http.StatusUnauthorized) http.Error(w, "Authorization header required", http.StatusUnauthorized)
return return
} }
@@ -43,101 +43,112 @@ func AuthMiddleware(jwtSecret string) func(http.Handler) http.Handler {
// Ожидаем формат "Bearer <token>" // Ожидаем формат "Bearer <token>"
parts := strings.Split(authHeader, " ") parts := strings.Split(authHeader, " ")
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" { if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
l.Warn("Неверный формат заголовка Authorization", l.Debug("Invalid authorization header format",
zap.Int("parts_count", len(parts)), zap.String("header", authHeader))
zap.String("first_part", parts[0]))
http.Error(w, "Invalid authorization header format", http.StatusUnauthorized) http.Error(w, "Invalid authorization header format", http.StatusUnauthorized)
return return
} }
tokenString := parts[1] tokenString := parts[1]
l.Info("Token extracted", zap.String("token_preview", tokenString[:min(20, len(tokenString))]+"..."))
// Парсим и валидируем токен // Парсим и валидируем токен
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { claims, err := validateToken(tokenString, jwtSecret)
// Проверяем метод подписи
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
l.Error("Unexpected signing method",
zap.String("method", token.Method.Alg()))
return nil, jwt.ErrSignatureInvalid
}
return []byte(jwtSecret), nil
})
if err != nil { if err != nil {
l.Error("Token parse error", zap.Error(err)) l.Debug("Token validation failed", zap.Error(err))
http.Error(w, "Invalid token: "+err.Error(), http.StatusUnauthorized)
return // Возвращаем разные сообщения в зависимости от ошибки
} if err == jwt.ErrTokenExpired {
http.Error(w, "Token expired", http.StatusUnauthorized)
if !token.Valid { return
l.Error("Token is not valid") }
http.Error(w, "Invalid token", http.StatusUnauthorized) http.Error(w, "Invalid token", http.StatusUnauthorized)
return return
} }
l.Info("Token is valid")
// Извлекаем claims
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
l.Error("Failed to extract claims")
http.Error(w, "Invalid token claims", http.StatusUnauthorized)
return
}
l.Info("Claims extracted", zap.Any("claims", claims))
// Проверяем тип токена (должен быть access)
if tokenType, exists := claims["type"]; exists {
l.Info("Token type", zap.String("type", tokenType.(string)))
if tokenType != "access" {
l.Error("Wrong token type, expected access", zap.String("type", tokenType.(string)))
http.Error(w, "Invalid token type", http.StatusUnauthorized)
return
}
}
// Добавляем информацию о пользователе в контекст // Добавляем информацию о пользователе в контекст
ctx := r.Context() ctx := addUserToContext(r.Context(), claims)
// Извлекаем userID из sub (subject) // Логируем успешную аутентификацию (только в debug)
// В claims sub хранится как string, а не float64 if userID, ok := ctx.Value(UserIDKey).(uint); ok {
if userID, ok := claims["sub"].(string); ok { l.Debug("User authenticated",
l.Info("User ID from claims", zap.String("user_id_str", userID)) zap.Uint("user_id", userID),
// Конвертируем string в uint zap.String("path", r.URL.Path))
var userIDUint uint
if _, err := fmt.Sscan(userID, &userIDUint); err == nil {
ctx = context.WithValue(ctx, UserIDKey, userIDUint)
l.Info("User ID added to context", zap.Uint("user_id", userIDUint))
}
} else {
l.Error("sub claim not found or wrong type")
}
// Извлекаем email
if email, ok := claims["email"].(string); ok {
ctx = context.WithValue(ctx, UserEmailKey, email)
l.Info("Email added to context", zap.String("email", email))
}
// Извлекаем роль
if role, ok := claims["role"].(string); ok {
ctx = context.WithValue(ctx, UserRoleKey, role)
l.Info("Role added to context", zap.String("role", role))
} }
l.Info("=== AUTH MIDDLEWARE END ===")
// Передаем управление дальше с обновленным контекстом // Передаем управление дальше с обновленным контекстом
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))
}) })
} }
} }
func min(a, b int) int { // validateToken валидирует JWT токен и возвращает claims
if a < b { func validateToken(tokenString, jwtSecret string) (jwt.MapClaims, error) {
return a token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Проверяем метод подписи
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(jwtSecret), nil
})
if err != nil {
return nil, err
} }
return b
if !token.Valid {
return nil, fmt.Errorf("invalid token")
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, fmt.Errorf("invalid claims")
}
// Проверяем тип токена (должен быть access)
if tokenType, exists := claims["type"]; !exists || tokenType != "access" {
return nil, fmt.Errorf("invalid token type, expected access")
}
return claims, nil
}
// addUserToContext добавляет информацию о пользователе в контекст
func addUserToContext(ctx context.Context, claims jwt.MapClaims) context.Context {
// Извлекаем userID из sub (subject)
if userIDStr, ok := claims["sub"].(string); ok {
var userID uint
if _, err := fmt.Sscan(userIDStr, &userID); err == nil {
ctx = context.WithValue(ctx, UserIDKey, userID)
}
}
// Извлекаем email
if email, ok := claims["email"].(string); ok {
ctx = context.WithValue(ctx, UserEmailKey, email)
}
// Извлекаем роль
if role, ok := claims["role"].(string); ok {
ctx = context.WithValue(ctx, UserRoleKey, role)
}
return ctx
}
// GetUserID извлекает ID пользователя из контекста
func GetUserID(ctx context.Context) (uint, bool) {
userID, ok := ctx.Value(UserIDKey).(uint)
return userID, ok
}
// GetUserEmail извлекает email пользователя из контекста
func GetUserEmail(ctx context.Context) (string, bool) {
email, ok := ctx.Value(UserEmailKey).(string)
return email, ok
}
// GetUserRole извлекает роль пользователя из контекста
func GetUserRole(ctx context.Context) (string, bool) {
role, ok := ctx.Value(UserRoleKey).(string)
return role, ok
} }
@@ -0,0 +1,20 @@
package middleware
import (
"context"
)
// WithUserID добавляет ID пользователя в контекст (для тестирования)
func WithUserID(ctx context.Context, userID uint) context.Context {
return context.WithValue(ctx, UserIDKey, userID)
}
// WithUserEmail добавляет email пользователя в контекст
func WithUserEmail(ctx context.Context, email string) context.Context {
return context.WithValue(ctx, UserEmailKey, email)
}
// WithUserRole добавляет роль пользователя в контекст
func WithUserRole(ctx context.Context, role string) context.Context {
return context.WithValue(ctx, UserRoleKey, role)
}
@@ -0,0 +1,67 @@
package middleware
import (
"bytes"
"io"
"net/http"
"time"
"api_yal/internal/logger"
"github.com/go-chi/chi/v5/middleware"
"go.uber.org/zap"
)
// RequestLoggerMiddleware логирует все HTTP запросы
func RequestLoggerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
l := logger.Get()
start := time.Now()
// Создаем кастомный ResponseWriter для захвата статуса
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
// Логируем тело запроса только для определенных методов (опционально)
var body []byte
if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
body, _ = io.ReadAll(r.Body)
r.Body = io.NopCloser(bytes.NewBuffer(body))
}
// Обрабатываем запрос
next.ServeHTTP(ww, r)
// Логируем результат
duration := time.Since(start)
logFields := []zap.Field{
zap.String("method", r.Method),
zap.String("path", r.URL.Path),
zap.Int("status", ww.Status()),
zap.Duration("duration", duration),
zap.String("remote_addr", r.RemoteAddr),
zap.String("user_agent", r.UserAgent()),
}
// Добавляем ID пользователя, если есть
if userID, ok := GetUserID(r.Context()); ok {
logFields = append(logFields, zap.Uint("user_id", userID))
}
// Логируем тело запроса для ошибок
if ww.Status() >= 400 && len(body) > 0 {
logFields = append(logFields, zap.ByteString("request_body", body))
}
// Выбираем уровень логирования в зависимости от статуса
switch {
case ww.Status() >= 500:
l.Error("Request failed", logFields...)
case ww.Status() >= 400:
l.Warn("Request error", logFields...)
default:
l.Info("Request completed", logFields...)
}
})
}
@@ -4,6 +4,7 @@ import (
"api_yal/internal/config" "api_yal/internal/config"
"api_yal/internal/logger" "api_yal/internal/logger"
"api_yal/internal/domain/auth" "api_yal/internal/domain/auth"
"api_yal/internal/domain/account"
"time" "time"
"encoding/json" "encoding/json"
@@ -45,9 +46,14 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
}) })
zapLogger.Debug("Health check маршрут зарегистрирован") zapLogger.Debug("Health check маршрут зарегистрирован")
// Здесь можно добавить другие маршруты, которые будут защищены аутентификацией // Группируем API маршруты под /api/v1
auth.RegisterRoutes(r, db, config.JWTSecret) r.Route("/api/v1", func(r chi.Router) {
// r.Mount("/api/v1", apiRoutes(db, config)) // Регистрируем маршруты аутентификации
auth.RegisterRoutes(r, db, config.JWTSecret)
// Регистрируем маршруты аккаунтов
account.RegisterRoutes(r, db, config.JWTSecret)
})
zapLogger.Info("Настройка маршрутов завершена") zapLogger.Info("Настройка маршрутов завершена")