On branch main
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 modified: main_dc/yalarba/api_yal/internal/domain/auth/router.go new file: main_dc/yalarba/api_yal/internal/domain/comment/dto.go new file: main_dc/yalarba/api_yal/internal/domain/feetback/dto.go new file: main_dc/yalarba/api_yal/internal/domain/object/dto.go new file: main_dc/yalarba/api_yal/internal/domain/object/errors.go new file: main_dc/yalarba/api_yal/internal/domain/object/handler.go new file: main_dc/yalarba/api_yal/internal/domain/object/router.go new file: main_dc/yalarba/api_yal/internal/domain/object/service.go new file: main_dc/yalarba/api_yal/internal/domain/object/types.go new file: main_dc/yalarba/api_yal/internal/domain/rating/dto.go modified: main_dc/yalarba/api_yal/internal/models/rating.go add and not tested Object's domain
This commit is contained in:
@@ -14,22 +14,22 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Handler обработчик для операций с аккаунтами
|
||||
type Handler struct {
|
||||
service Service
|
||||
// AccountHandler обработчик для операций с аккаунтами
|
||||
type AccountHandler struct {
|
||||
service AccountService
|
||||
validator *validator.Validate
|
||||
}
|
||||
|
||||
// NewHandler создает новый экземпляр Handler
|
||||
func NewHandler(service Service) *Handler {
|
||||
return &Handler{
|
||||
func NewHandler(service AccountService) *AccountHandler {
|
||||
return &AccountHandler{
|
||||
service: service,
|
||||
validator: validator.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetAccountByID получение аккаунта по ID
|
||||
func (h *Handler) GetAccountByID(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *AccountHandler) GetAccountByID(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
|
||||
// Получаем ID из контекста (для своего профиля)
|
||||
@@ -59,7 +59,7 @@ func (h *Handler) GetAccountByID(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// GetAccountProfile получение профиля пользователя
|
||||
func (h *Handler) GetAccountProfile(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *AccountHandler) GetAccountProfile(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
|
||||
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
|
||||
@@ -86,7 +86,7 @@ func (h *Handler) GetAccountProfile(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// UpdateAccount обновление аккаунта
|
||||
func (h *Handler) UpdateAccount(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *AccountHandler) UpdateAccount(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
|
||||
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
|
||||
@@ -120,7 +120,7 @@ func (h *Handler) UpdateAccount(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// ChangePassword смена пароля
|
||||
func (h *Handler) ChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *AccountHandler) ChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
|
||||
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
|
||||
@@ -163,82 +163,8 @@ func (h *Handler) ChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func (h *AccountHandler) DeleteAccount(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
|
||||
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
|
||||
@@ -268,7 +194,7 @@ func (h *Handler) DeleteAccount(w http.ResponseWriter, r *http.Request) {
|
||||
// ==================== Административные методы ====================
|
||||
|
||||
// ListAccounts список аккаунтов (админ)
|
||||
func (h *Handler) ListAccounts(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *AccountHandler) ListAccounts(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
|
||||
var req ListAccountsRequest
|
||||
@@ -306,7 +232,7 @@ func (h *Handler) ListAccounts(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// GetAccountByIDAdmin получение аккаунта по ID (админ)
|
||||
func (h *Handler) GetAccountByIDAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *AccountHandler) GetAccountByIDAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
|
||||
// Получаем ID из URL
|
||||
@@ -340,7 +266,7 @@ func (h *Handler) GetAccountByIDAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// VerifyAccount верификация аккаунта (админ)
|
||||
func (h *Handler) VerifyAccount(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *AccountHandler) VerifyAccount(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
|
||||
// Получаем ID из URL
|
||||
@@ -381,7 +307,7 @@ func (h *Handler) VerifyAccount(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// UpdateAccountStatus обновление статуса аккаунта (админ)
|
||||
func (h *Handler) UpdateAccountStatus(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *AccountHandler) UpdateAccountStatus(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
|
||||
// Получаем ID из URL
|
||||
@@ -427,7 +353,7 @@ func (h *Handler) UpdateAccountStatus(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// handleValidationError обрабатывает ошибки валидации
|
||||
func (h *Handler) handleValidationError(w http.ResponseWriter, err error) {
|
||||
func (h *AccountHandler) handleValidationError(w http.ResponseWriter, err error) {
|
||||
var invalidValidationError *validator.InvalidValidationError
|
||||
if errors.As(err, &invalidValidationError) {
|
||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
|
||||
@@ -19,12 +19,6 @@ func RegisterRoutes(r chi.Router, db *gorm.DB, jwtSecret string) {
|
||||
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))
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"api_yal/internal/logger"
|
||||
"api_yal/internal/models"
|
||||
"api_yal/internal/repository"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
@@ -13,29 +15,27 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Service интерфейс сервиса аккаунтов
|
||||
type Service interface {
|
||||
// AccountService интерфейс сервиса аккаунтов
|
||||
type AccountService 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)
|
||||
@@ -43,20 +43,20 @@ type Service interface {
|
||||
UpdateAccountModel(account *models.Account) error
|
||||
}
|
||||
|
||||
// serviceImpl реализация сервиса аккаунтов
|
||||
type serviceImpl struct {
|
||||
// accountServiceImpl реализация сервиса аккаунтов
|
||||
type accountServiceImpl struct {
|
||||
accountRepo repository.AccountRepository
|
||||
}
|
||||
|
||||
// NewService создает новый экземпляр сервиса аккаунтов
|
||||
func NewService(accountRepo repository.AccountRepository) Service {
|
||||
return &serviceImpl{
|
||||
func NewService(accountRepo repository.AccountRepository) AccountService {
|
||||
return &accountServiceImpl{
|
||||
accountRepo: accountRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// GetAccountByID получает аккаунт по ID
|
||||
func (s *serviceImpl) GetAccountByID(id uint) (*AccountResponse, error) {
|
||||
func (s *accountServiceImpl) GetAccountByID(id uint) (*AccountResponse, error) {
|
||||
l := logger.Get()
|
||||
l.Debug("Получение аккаунта по ID", zap.Uint("id", id))
|
||||
|
||||
@@ -74,7 +74,7 @@ func (s *serviceImpl) GetAccountByID(id uint) (*AccountResponse, error) {
|
||||
}
|
||||
|
||||
// GetAccountByEmail получает аккаунт по email
|
||||
func (s *serviceImpl) GetAccountByEmail(email string) (*AccountResponse, error) {
|
||||
func (s *accountServiceImpl) GetAccountByEmail(email string) (*AccountResponse, error) {
|
||||
l := logger.Get()
|
||||
l.Debug("Получение аккаунта по email", zap.String("email", email))
|
||||
|
||||
@@ -92,7 +92,7 @@ func (s *serviceImpl) GetAccountByEmail(email string) (*AccountResponse, error)
|
||||
}
|
||||
|
||||
// GetAccountWithObjects получает аккаунт с его объектами
|
||||
func (s *serviceImpl) GetAccountWithObjects(id uint) (*AccountWithObjectsResponse, error) {
|
||||
func (s *accountServiceImpl) GetAccountWithObjects(id uint) (*AccountWithObjectsResponse, error) {
|
||||
l := logger.Get()
|
||||
l.Debug("Получение аккаунта с объектами", zap.Uint("id", id))
|
||||
|
||||
@@ -117,7 +117,7 @@ func (s *serviceImpl) GetAccountWithObjects(id uint) (*AccountWithObjectsRespons
|
||||
}
|
||||
|
||||
// UpdateAccount обновляет информацию об аккаунте
|
||||
func (s *serviceImpl) UpdateAccount(id uint, req UpdateAccountRequest) (*AccountResponse, error) {
|
||||
func (s *accountServiceImpl) UpdateAccount(id uint, req UpdateAccountRequest) (*AccountResponse, error) {
|
||||
l := logger.Get()
|
||||
l.Info("Обновление аккаунта", zap.Uint("id", id))
|
||||
|
||||
@@ -171,7 +171,7 @@ func (s *serviceImpl) UpdateAccount(id uint, req UpdateAccountRequest) (*Account
|
||||
}
|
||||
|
||||
// DeleteAccount удаляет аккаунт (мягкое удаление)
|
||||
func (s *serviceImpl) DeleteAccount(id uint) error {
|
||||
func (s *accountServiceImpl) DeleteAccount(id uint) error {
|
||||
l := logger.Get()
|
||||
l.Info("Удаление аккаунта", zap.Uint("id", id))
|
||||
|
||||
@@ -184,7 +184,7 @@ func (s *serviceImpl) DeleteAccount(id uint) error {
|
||||
}
|
||||
|
||||
// ChangePassword изменяет пароль пользователя
|
||||
func (s *serviceImpl) ChangePassword(userID uint, req ChangePasswordRequest) error {
|
||||
func (s *accountServiceImpl) ChangePassword(userID uint, req ChangePasswordRequest) error {
|
||||
l := logger.Get()
|
||||
l.Info("Смена пароля", zap.Uint("userID", userID))
|
||||
|
||||
@@ -220,7 +220,7 @@ func (s *serviceImpl) ChangePassword(userID uint, req ChangePasswordRequest) err
|
||||
}
|
||||
|
||||
// ForgotPassword запрашивает сброс пароля
|
||||
func (s *serviceImpl) ForgotPassword(email string) (string, error) {
|
||||
func (s *accountServiceImpl) ForgotPassword(email string) (string, error) {
|
||||
l := logger.Get()
|
||||
l.Info("Запрос сброса пароля", zap.String("email", email))
|
||||
|
||||
@@ -235,7 +235,11 @@ func (s *serviceImpl) ForgotPassword(email string) (string, error) {
|
||||
|
||||
// Генерируем reset token (используем метод из auth сервиса)
|
||||
// В реальном приложении здесь должна быть генерация токена
|
||||
resetToken := generateResetToken()
|
||||
resetToken, err := generateResetToken()
|
||||
if err != nil {
|
||||
l.Error("Ошибка генерации reset token", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
|
||||
passwordReset := &models.PasswordReset{
|
||||
AccountID: account.ID,
|
||||
@@ -254,7 +258,7 @@ func (s *serviceImpl) ForgotPassword(email string) (string, error) {
|
||||
}
|
||||
|
||||
// ResetPassword сбрасывает пароль по токену
|
||||
func (s *serviceImpl) ResetPassword(token, newPassword string) error {
|
||||
func (s *accountServiceImpl) ResetPassword(token, newPassword string) error {
|
||||
l := logger.Get()
|
||||
l.Info("Сброс пароля по токену")
|
||||
|
||||
@@ -307,7 +311,7 @@ func (s *serviceImpl) ResetPassword(token, newPassword string) error {
|
||||
}
|
||||
|
||||
// ListAccounts возвращает список аккаунтов с пагинацией
|
||||
func (s *serviceImpl) ListAccounts(req ListAccountsRequest) (*AccountListResponse, error) {
|
||||
func (s *accountServiceImpl) ListAccounts(req ListAccountsRequest) (*AccountListResponse, error) {
|
||||
l := logger.Get()
|
||||
l.Debug("Получение списка аккаунтов", zap.Any("request", req))
|
||||
|
||||
@@ -320,7 +324,7 @@ func (s *serviceImpl) ListAccounts(req ListAccountsRequest) (*AccountListRespons
|
||||
}
|
||||
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
|
||||
|
||||
var accounts []models.Account
|
||||
var total int64
|
||||
var err error
|
||||
@@ -341,14 +345,14 @@ func (s *serviceImpl) ListAccounts(req ListAccountsRequest) (*AccountListRespons
|
||||
}
|
||||
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)
|
||||
@@ -369,7 +373,7 @@ func (s *serviceImpl) ListAccounts(req ListAccountsRequest) (*AccountListRespons
|
||||
}
|
||||
|
||||
// VerifyAccount верифицирует аккаунт
|
||||
func (s *serviceImpl) VerifyAccount(accountID uint, req VerifyAccountRequest) error {
|
||||
func (s *accountServiceImpl) VerifyAccount(accountID uint, req VerifyAccountRequest) error {
|
||||
l := logger.Get()
|
||||
l.Info("Верификация аккаунта", zap.Uint("id", accountID))
|
||||
|
||||
@@ -392,7 +396,7 @@ func (s *serviceImpl) VerifyAccount(accountID uint, req VerifyAccountRequest) er
|
||||
}
|
||||
|
||||
// UpdateAccountStatus обновляет статус аккаунта (админ)
|
||||
func (s *serviceImpl) UpdateAccountStatus(accountID uint, req UpdateAccountStatusRequest) error {
|
||||
func (s *accountServiceImpl) UpdateAccountStatus(accountID uint, req UpdateAccountStatusRequest) error {
|
||||
l := logger.Get()
|
||||
l.Info("Обновление статуса аккаунта", zap.Uint("id", accountID))
|
||||
|
||||
@@ -419,13 +423,13 @@ func (s *serviceImpl) UpdateAccountStatus(accountID uint, req UpdateAccountStatu
|
||||
}
|
||||
|
||||
// GetAccountStats получает статистику аккаунта
|
||||
func (s *serviceImpl) GetAccountStats(userID uint) (*AccountStats, error) {
|
||||
func (s *accountServiceImpl) GetAccountStats(userID uint) (*AccountStats, error) {
|
||||
l := logger.Get()
|
||||
l.Debug("Получение статистики аккаунта", zap.Uint("userID", userID))
|
||||
|
||||
// Здесь должна быть реальная логика подсчета статистики
|
||||
// В реальном приложении нужно запрашивать данные из соответствующих репозиториев
|
||||
|
||||
|
||||
stats := &AccountStats{
|
||||
ObjectsCount: 0,
|
||||
FeedbacksCount: 0,
|
||||
@@ -433,14 +437,14 @@ func (s *serviceImpl) GetAccountStats(userID uint) (*AccountStats, error) {
|
||||
RatingsCount: 0,
|
||||
AppealsCount: 0,
|
||||
}
|
||||
|
||||
|
||||
// TODO: Получить реальную статистику из базы данных
|
||||
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// GetAccountProfile получает профиль пользователя со статистикой
|
||||
func (s *serviceImpl) GetAccountProfile(userID uint) (*AccountProfileResponse, error) {
|
||||
func (s *accountServiceImpl) GetAccountProfile(userID uint) (*AccountProfileResponse, error) {
|
||||
l := logger.Get()
|
||||
l.Debug("Получение профиля пользователя", zap.Uint("userID", userID))
|
||||
|
||||
@@ -465,17 +469,17 @@ func (s *serviceImpl) GetAccountProfile(userID uint) (*AccountProfileResponse, e
|
||||
}
|
||||
|
||||
// GetAccountModelByID получает модель аккаунта по ID (для внутреннего использования)
|
||||
func (s *serviceImpl) GetAccountModelByID(id uint) (*models.Account, error) {
|
||||
func (s *accountServiceImpl) GetAccountModelByID(id uint) (*models.Account, error) {
|
||||
return s.accountRepo.GetByID(id)
|
||||
}
|
||||
|
||||
// GetAccountModelByEmail получает модель аккаунта по email (для внутреннего использования)
|
||||
func (s *serviceImpl) GetAccountModelByEmail(email string) (*models.Account, error) {
|
||||
func (s *accountServiceImpl) GetAccountModelByEmail(email string) (*models.Account, error) {
|
||||
return s.accountRepo.GetByEmail(email)
|
||||
}
|
||||
|
||||
// CreateAccount создает новый аккаунт
|
||||
func (s *serviceImpl) CreateAccount(req CreateAccountRequest) (*models.Account, error) {
|
||||
func (s *accountServiceImpl) CreateAccount(req CreateAccountRequest) (*models.Account, error) {
|
||||
l := logger.Get()
|
||||
l.Info("Создание аккаунта", zap.String("email", req.Email))
|
||||
|
||||
@@ -526,21 +530,21 @@ func (s *serviceImpl) CreateAccount(req CreateAccountRequest) (*models.Account,
|
||||
}
|
||||
|
||||
// UpdateAccountModel обновляет модель аккаунта
|
||||
func (s *serviceImpl) UpdateAccountModel(account *models.Account) error {
|
||||
func (s *accountServiceImpl) UpdateAccountModel(account *models.Account) error {
|
||||
return s.accountRepo.Update(account)
|
||||
}
|
||||
|
||||
// Вспомогательные методы
|
||||
|
||||
func (s *serviceImpl) getSearchTotal(query string) (int64, error) {
|
||||
func (s *accountServiceImpl) getSearchTotal(query string) (int64, error) {
|
||||
// Здесь должна быть реализация подсчета общего количества результатов поиска
|
||||
// Для простоты возвращаем 0
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (s *serviceImpl) filterAccounts(accounts []models.Account, role string, isActive *bool) []models.Account {
|
||||
func (s *accountServiceImpl) filterAccounts(accounts []models.Account, role string, isActive *bool) []models.Account {
|
||||
var filtered []models.Account
|
||||
|
||||
|
||||
for _, acc := range accounts {
|
||||
if role != "" && acc.Role != role {
|
||||
continue
|
||||
@@ -550,12 +554,17 @@ func (s *serviceImpl) filterAccounts(accounts []models.Account, role string, isA
|
||||
}
|
||||
filtered = append(filtered, acc)
|
||||
}
|
||||
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
// Генерация reset токена
|
||||
func generateResetToken() string {
|
||||
// В реальном приложении используйте криптографически безопасную генерацию
|
||||
return fmt.Sprintf("reset_%d_%d", time.Now().UnixNano(), time.Now().Unix())
|
||||
}
|
||||
func generateResetToken() (string, error) {
|
||||
// Генерируем 32 байта (64 символа в hex) — достаточно для безопасности
|
||||
bytes := make([]byte, 32)
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate token: %w", err)
|
||||
}
|
||||
return hex.EncodeToString(bytes), nil
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
|
||||
// RegisterRoutes регистрирует маршруты аутентификации
|
||||
func RegisterRoutes(r chi.Router, db *gorm.DB, jwtSecret string) {
|
||||
l := logger.Get()
|
||||
l.Debug("Регистрация маршрутов для auth.go")
|
||||
// Создаем репозиторий и сервис
|
||||
accountRepo := repository.NewAccountRepository(db)
|
||||
|
||||
@@ -25,7 +27,6 @@ func RegisterRoutes(r chi.Router, db *gorm.DB, jwtSecret string) {
|
||||
authService := NewAuthService(accountRepo, authConfig)
|
||||
handler := NewAuthHandler(authService)
|
||||
|
||||
l := logger.Get()
|
||||
l.Debug("Регистрация маршрутов аутентификации")
|
||||
|
||||
r.Route("/auth", func(r chi.Router) {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package comment
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// CommentShortResponse - краткий ответ для комментария
|
||||
type CommentShortResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
OwnerID uint `json:"owner_id"`
|
||||
OwnerName string `json:"owner_name,omitempty"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// CreateCommentRequest - DTO для создания комментария
|
||||
type CreateCommentRequest struct {
|
||||
FeedbackID uint `json:"feedback_id" binding:"required"`
|
||||
Text string `json:"text" binding:"required"`
|
||||
}
|
||||
|
||||
// UpdateCommentRequest - DTO для обновления комментария
|
||||
type UpdateCommentRequest struct {
|
||||
Text *string `json:"text" binding:"required"`
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package feetback
|
||||
|
||||
import (
|
||||
|
||||
)
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"api_yal/internal/domain/account"
|
||||
"api_yal/internal/domain/comment"
|
||||
"time"
|
||||
"api_yal/internal/models"
|
||||
)
|
||||
|
||||
// ==================== Object DTOs ====================
|
||||
|
||||
// CreateObjectRequest - DTO для создания объекта
|
||||
type CreateObjectRequest struct {
|
||||
OwnerID uint `json:"owner_id" binding:"required"`
|
||||
ShortName string `json:"short_name" binding:"required,min=1,max=255"`
|
||||
LongName string `json:"long_name"`
|
||||
Type string `json:"type"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email" binding:"omitempty,email"`
|
||||
Site string `json:"site" binding:"omitempty,url"`
|
||||
ShortDescription string `json:"short_description"`
|
||||
Description string `json:"description"`
|
||||
Address string `json:"address"`
|
||||
Latitude float64 `json:"latitude" binding:"omitempty,latitude"`
|
||||
Longitude float64 `json:"longitude" binding:"omitempty,longitude"`
|
||||
IsActive *bool `json:"is_active"` // указатель, чтобы отличать false от отсутствия значения
|
||||
IsVerified *bool `json:"is_verified"`
|
||||
}
|
||||
|
||||
// UpdateObjectRequest - DTO для обновления объекта (все поля опциональны)
|
||||
type UpdateObjectRequest struct {
|
||||
ShortName *string `json:"short_name" binding:"omitempty,min=1,max=255"`
|
||||
LongName *string `json:"long_name"`
|
||||
Type *string `json:"type"`
|
||||
Phone *string `json:"phone"`
|
||||
Email *string `json:"email" binding:"omitempty,email"`
|
||||
Site *string `json:"site" binding:"omitempty,url"`
|
||||
ShortDescription *string `json:"short_description"`
|
||||
Description *string `json:"description"`
|
||||
Address *string `json:"address"`
|
||||
Latitude *float64 `json:"latitude" binding:"omitempty,latitude"`
|
||||
Longitude *float64 `json:"longitude" binding:"omitempty,longitude"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
IsVerified *bool `json:"is_verified"`
|
||||
}
|
||||
|
||||
// ObjectResponse - DTO для полного ответа с объектом (включая связанные данные)
|
||||
type ObjectResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt *time.Time `json:"deleted_at,omitempty"`
|
||||
OwnerID uint `json:"owner_id"`
|
||||
Owner *account.AccountResponse `json:"owner,omitempty"`
|
||||
ShortName string `json:"short_name"`
|
||||
LongName string `json:"long_name"`
|
||||
Type string `json:"type"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
Site string `json:"site"`
|
||||
ShortDescription string `json:"short_description"`
|
||||
Description string `json:"description"`
|
||||
Address string `json:"address"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
IsActive bool `json:"is_active"`
|
||||
IsVerified bool `json:"is_verified"`
|
||||
FeedbackCount int `json:"feedback_count"`
|
||||
TouristRating *RatingResponse `json:"tourist_rating,omitempty"`
|
||||
EntrepreneurRating *RatingResponse `json:"entrepreneur_rating,omitempty"`
|
||||
Feedbacks []FeedbackShortResponse `json:"feedbacks,omitempty"`
|
||||
}
|
||||
|
||||
// ObjectShortResponse - DTO для краткого ответа (списки, вложенные данные)
|
||||
type ObjectShortResponse struct {
|
||||
ID uint `json:"id"`
|
||||
ShortName string `json:"short_name"`
|
||||
LongName string `json:"long_name"`
|
||||
Type string `json:"type"`
|
||||
Address string `json:"address"`
|
||||
IsActive bool `json:"is_active"`
|
||||
IsVerified bool `json:"is_verified"`
|
||||
FeedbackCount int `json:"feedback_count"`
|
||||
// Агрегированные рейтинги для списка
|
||||
TouristAverageScore float64 `json:"tourist_average_score,omitempty"`
|
||||
EntrepreneurAverageScore float64 `json:"entrepreneur_average_score,omitempty"`
|
||||
}
|
||||
|
||||
// ObjectListResponse - DTO для списка объектов с пагинацией
|
||||
type ObjectListResponse struct {
|
||||
Items []ObjectShortResponse `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
// ==================== Feedback DTOs ====================
|
||||
|
||||
// CreateFeedbackRequest - DTO для создания отзыва
|
||||
type CreateFeedbackRequest struct {
|
||||
ObjectID uint `json:"object_id" binding:"required"`
|
||||
Platform models.PlatformType `json:"platform" binding:"required,oneof=entrepreneur tourist"`
|
||||
Score int `json:"score" binding:"required,min=1,max=5"`
|
||||
Text string `json:"text" binding:"required"`
|
||||
}
|
||||
|
||||
// UpdateFeedbackRequest - DTO для обновления отзыва
|
||||
type UpdateFeedbackRequest struct {
|
||||
Score *int `json:"score" binding:"omitempty,min=1,max=5"`
|
||||
Text *string `json:"text"`
|
||||
}
|
||||
|
||||
// FeedbackResponse - DTO для полного ответа с отзывом
|
||||
type FeedbackResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt *time.Time `json:"deleted_at,omitempty"`
|
||||
OwnerID uint `json:"owner_id"`
|
||||
Owner *account.AccountResponse `json:"owner,omitempty"`
|
||||
ObjectID uint `json:"object_id"`
|
||||
Object *ObjectShortResponse `json:"object,omitempty"`
|
||||
Platform models.PlatformType `json:"platform"`
|
||||
Score int `json:"score"`
|
||||
Text string `json:"text"`
|
||||
CommentCount int `json:"comment_count"`
|
||||
Comments []comment.CommentShortResponse `json:"comments,omitempty"`
|
||||
}
|
||||
|
||||
// FeedbackShortResponse - DTO для краткого ответа (вложенный в объект)
|
||||
type FeedbackShortResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
OwnerID uint `json:"owner_id"`
|
||||
OwnerName string `json:"owner_name,omitempty"` // из Account
|
||||
Platform models.PlatformType `json:"platform"`
|
||||
Score int `json:"score"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// ==================== Rating DTOs ====================
|
||||
|
||||
// RatingResponse - DTO для ответа с рейтингом
|
||||
type RatingResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Platform models.PlatformType `json:"platform"`
|
||||
AverageScore float64 `json:"average_score"`
|
||||
TotalVotes int `json:"total_votes"`
|
||||
VoteBreakdown VoteBreakdownDTO `json:"vote_breakdown"`
|
||||
}
|
||||
|
||||
// VoteBreakdownDTO - DTO для детализации оценок
|
||||
type VoteBreakdownDTO struct {
|
||||
Score1 int `json:"score_1"`
|
||||
Score2 int `json:"score_2"`
|
||||
Score3 int `json:"score_3"`
|
||||
Score4 int `json:"score_4"`
|
||||
Score5 int `json:"score_5"`
|
||||
}
|
||||
|
||||
// CreateRatingVoteRequest - DTO для голосования
|
||||
type CreateRatingVoteRequest struct {
|
||||
Platform models.PlatformType `json:"platform" binding:"required,oneof=entrepreneur tourist"`
|
||||
TargetID uint `json:"target_id" binding:"required"` // ObjectID
|
||||
Score int `json:"score" binding:"required,min=1,max=5"`
|
||||
}
|
||||
|
||||
// RatingVoteResponse - DTO для ответа о голосе пользователя
|
||||
type RatingVoteResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Platform models.PlatformType `json:"platform"`
|
||||
TargetID uint `json:"target_id"`
|
||||
VoterID uint `json:"voter_id"`
|
||||
Score int `json:"score"`
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package object
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrObjectNotFound = errors.New("object not found")
|
||||
ErrInvalidOwnerID = errors.New("invalid owner ID")
|
||||
ErrShortNameRequired = errors.New("short name is required")
|
||||
ErrAlreadyVoted = errors.New("user has already voted")
|
||||
ErrNotImplemented = errors.New("feature not implemented yet")
|
||||
)
|
||||
@@ -0,0 +1,296 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"api_yal/internal/middleware"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type ObjectHandler struct {
|
||||
objectService ObjectService
|
||||
}
|
||||
|
||||
func NewObjectHandler(objectService ObjectService) *ObjectHandler {
|
||||
return &ObjectHandler{
|
||||
objectService: objectService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetObjectByID обрабатывает GET /objects/{id}
|
||||
func (h *ObjectHandler) GetObjectByID(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid object ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.objectService.GetObjectByID(r.Context(), uint(id))
|
||||
if err != nil {
|
||||
h.handleError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.respondWithJSON(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// CreateObject обрабатывает POST /objects
|
||||
func (h *ObjectHandler) CreateObject(w http.ResponseWriter, r *http.Request) {
|
||||
var req CreateObjectRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Получаем ID пользователя из контекста (из AuthMiddleware)
|
||||
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
|
||||
if !ok {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Устанавливаем owner_id из аутентифицированного пользователя
|
||||
req.OwnerID = userID
|
||||
|
||||
response, err := h.objectService.CreateObject(r.Context(), &req)
|
||||
if err != nil {
|
||||
h.handleError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.respondWithJSON(w, http.StatusCreated, response)
|
||||
}
|
||||
|
||||
// UpdateObject обрабатывает PUT /objects/{id}
|
||||
func (h *ObjectHandler) UpdateObject(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid object ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateObjectRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.objectService.UpdateObject(r.Context(), uint(id), &req)
|
||||
if err != nil {
|
||||
h.handleError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.respondWithJSON(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// DeleteObject обрабатывает DELETE /objects/{id}
|
||||
func (h *ObjectHandler) DeleteObject(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid object ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.objectService.DeleteObject(r.Context(), uint(id)); err != nil {
|
||||
h.handleError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// ListObjects обрабатывает GET /objects
|
||||
func (h *ObjectHandler) ListObjects(w http.ResponseWriter, r *http.Request) {
|
||||
req := &ListObjectsRequest{
|
||||
Page: h.getQueryParamInt(r, "page", 1),
|
||||
PageSize: h.getQueryParamInt(r, "page_size", 10),
|
||||
Type: r.URL.Query().Get("type"),
|
||||
Query: r.URL.Query().Get("q"),
|
||||
}
|
||||
|
||||
if statusStr := r.URL.Query().Get("is_active"); statusStr != "" {
|
||||
isActive, err := strconv.ParseBool(statusStr)
|
||||
if err == nil {
|
||||
req.Status = &isActive
|
||||
}
|
||||
}
|
||||
|
||||
response, err := h.objectService.ListObjects(r.Context(), req)
|
||||
if err != nil {
|
||||
h.handleError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.respondWithJSON(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// GetObjectsByOwner обрабатывает GET /objects/owner/{ownerId}
|
||||
func (h *ObjectHandler) GetObjectsByOwner(w http.ResponseWriter, r *http.Request) {
|
||||
ownerID, err := strconv.ParseUint(chi.URLParam(r, "ownerId"), 10, 32)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid owner ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
page := h.getQueryParamInt(r, "page", 1)
|
||||
pageSize := h.getQueryParamInt(r, "page_size", 10)
|
||||
|
||||
response, err := h.objectService.GetObjectsByOwner(r.Context(), uint(ownerID), page, pageSize)
|
||||
if err != nil {
|
||||
h.handleError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.respondWithJSON(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// SearchObjects обрабатывает GET /objects/search
|
||||
func (h *ObjectHandler) SearchObjects(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query().Get("q")
|
||||
if query == "" {
|
||||
http.Error(w, "Search query is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
page := h.getQueryParamInt(r, "page", 1)
|
||||
pageSize := h.getQueryParamInt(r, "page_size", 10)
|
||||
|
||||
response, err := h.objectService.SearchObjects(r.Context(), query, page, pageSize)
|
||||
if err != nil {
|
||||
h.handleError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.respondWithJSON(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// GetNearbyObjects обрабатывает GET /objects/nearby
|
||||
func (h *ObjectHandler) GetNearbyObjects(w http.ResponseWriter, r *http.Request) {
|
||||
lat, err := strconv.ParseFloat(r.URL.Query().Get("lat"), 64)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid latitude", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
lng, err := strconv.ParseFloat(r.URL.Query().Get("lng"), 64)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid longitude", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
radius, err := strconv.ParseFloat(r.URL.Query().Get("radius"), 64)
|
||||
if err != nil || radius <= 0 {
|
||||
radius = 1000 // По умолчанию 1 км
|
||||
}
|
||||
|
||||
page := h.getQueryParamInt(r, "page", 1)
|
||||
pageSize := h.getQueryParamInt(r, "page_size", 10)
|
||||
|
||||
response, err := h.objectService.GetNearbyObjects(r.Context(), lat, lng, radius, page, pageSize)
|
||||
if err != nil {
|
||||
h.handleError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.respondWithJSON(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// CreateFeedback обрабатывает POST /objects/{id}/feedbacks
|
||||
func (h *ObjectHandler) CreateFeedback(w http.ResponseWriter, r *http.Request) {
|
||||
objectID, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid object ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var req CreateFeedbackRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
req.ObjectID = uint(objectID)
|
||||
|
||||
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
|
||||
if !ok {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.objectService.CreateFeedback(r.Context(), &req, userID)
|
||||
if err != nil {
|
||||
h.handleError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.respondWithJSON(w, http.StatusCreated, response)
|
||||
}
|
||||
|
||||
// CreateRatingVote обрабатывает POST /objects/{id}/ratings
|
||||
func (h *ObjectHandler) CreateRatingVote(w http.ResponseWriter, r *http.Request) {
|
||||
objectID, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid object ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var req CreateRatingVoteRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
req.TargetID = uint(objectID)
|
||||
|
||||
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
|
||||
if !ok {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.objectService.CreateRatingVote(r.Context(), &req, userID)
|
||||
if err != nil {
|
||||
h.handleError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.respondWithJSON(w, http.StatusCreated, response)
|
||||
}
|
||||
|
||||
// Вспомогательные методы
|
||||
|
||||
func (h *ObjectHandler) respondWithJSON(w http.ResponseWriter, status int, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
if err := json.NewEncoder(w).Encode(data); err != nil {
|
||||
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ObjectHandler) handleError(w http.ResponseWriter, err error) {
|
||||
switch err {
|
||||
case ErrObjectNotFound:
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
case ErrInvalidOwnerID, ErrShortNameRequired, ErrAlreadyVoted:
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
case ErrNotImplemented:
|
||||
http.Error(w, err.Error(), http.StatusNotImplemented)
|
||||
default:
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ObjectHandler) getQueryParamInt(r *http.Request, key string, defaultValue int) int {
|
||||
value := r.URL.Query().Get(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
intValue, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
return intValue
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"api_yal/internal/logger"
|
||||
"api_yal/internal/middleware"
|
||||
"api_yal/internal/repository"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// RegisterObjects регистрирует маршруты для работы с объектами
|
||||
func RegisterObjects(r chi.Router, db *gorm.DB, jwtSecret string) {
|
||||
l := logger.Get()
|
||||
l.Debug("Регистрация маршрутов объектов")
|
||||
|
||||
objectRepo := repository.NewObjectRepository(db)
|
||||
objectService := NewObjectService(objectRepo)
|
||||
objectHandler := NewObjectHandler(objectService)
|
||||
|
||||
// Публичные маршруты (не требуют аутентификации)
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get("/objects", objectHandler.ListObjects)
|
||||
r.Get("/objects/search", objectHandler.SearchObjects)
|
||||
r.Get("/objects/nearby", objectHandler.GetNearbyObjects)
|
||||
r.Get("/objects/{id}", objectHandler.GetObjectByID)
|
||||
r.Get("/objects/owner/{ownerId}", objectHandler.GetObjectsByOwner)
|
||||
})
|
||||
|
||||
// Защищенные маршруты (требуют аутентификации)
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middleware.AuthMiddleware(jwtSecret))
|
||||
|
||||
// CRUD для объектов
|
||||
r.Post("/objects", objectHandler.CreateObject)
|
||||
r.Put("/objects/{id}", objectHandler.UpdateObject)
|
||||
r.Delete("/objects/{id}", objectHandler.DeleteObject)
|
||||
|
||||
// Отзывы
|
||||
r.Post("/objects/{id}/feedbacks", objectHandler.CreateFeedback)
|
||||
|
||||
// Рейтинги
|
||||
r.Post("/objects/{id}/ratings", objectHandler.CreateRatingVote)
|
||||
})
|
||||
|
||||
l.Debug("Маршруты объектов зарегистрированы")
|
||||
}
|
||||
@@ -0,0 +1,731 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"api_yal/internal/domain/account"
|
||||
"api_yal/internal/models"
|
||||
"api_yal/internal/repository"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ObjectService interface {
|
||||
GetObjectByID(ctx context.Context, id uint) (*ObjectResponse, error)
|
||||
CreateObject(ctx context.Context, req *CreateObjectRequest) (*ObjectResponse, error)
|
||||
UpdateObject(ctx context.Context, id uint, req *UpdateObjectRequest) (*ObjectResponse, error)
|
||||
DeleteObject(ctx context.Context, id uint) error
|
||||
ListObjects(ctx context.Context, req *ListObjectsRequest) (*ObjectListResponse, error)
|
||||
GetObjectsByOwner(ctx context.Context, ownerID uint, page, pageSize int) (*ObjectListResponse, error)
|
||||
GetObjectsByType(ctx context.Context, objectType string, page, pageSize int) (*ObjectListResponse, error)
|
||||
SearchObjects(ctx context.Context, query string, page, pageSize int) (*ObjectListResponse, error)
|
||||
GetNearbyObjects(ctx context.Context, lat, lng, radius float64, page, pageSize int) (*ObjectListResponse, error)
|
||||
ToggleVerification(ctx context.Context, id uint, verified bool) error
|
||||
|
||||
// Feedback methods
|
||||
CreateFeedback(ctx context.Context, req *CreateFeedbackRequest, ownerID uint) (*FeedbackResponse, error)
|
||||
UpdateFeedback(ctx context.Context, id uint, req *UpdateFeedbackRequest, ownerID uint) (*FeedbackResponse, error)
|
||||
DeleteFeedback(ctx context.Context, id uint, ownerID uint) error
|
||||
GetFeedbackByID(ctx context.Context, id uint) (*FeedbackResponse, error)
|
||||
GetFeedbacksByObject(ctx context.Context, objectID uint, page, pageSize int) (*FeedbackListResponse, error)
|
||||
|
||||
// Rating methods
|
||||
CreateRatingVote(ctx context.Context, req *CreateRatingVoteRequest, voterID uint) (*RatingVoteResponse, error)
|
||||
GetObjectRating(ctx context.Context, objectID uint, platform models.PlatformType) (*RatingResponse, error)
|
||||
GetUserRatingVote(ctx context.Context, objectID uint, userID uint, platform models.PlatformType) (*RatingVoteResponse, error)
|
||||
}
|
||||
|
||||
type objectServiceImpl struct {
|
||||
objectRepository repository.ObjectRepository
|
||||
}
|
||||
|
||||
func NewObjectService(objectRepository repository.ObjectRepository) ObjectService {
|
||||
return &objectServiceImpl{
|
||||
objectRepository: objectRepository,
|
||||
}
|
||||
}
|
||||
|
||||
// GetObjectByID возвращает объект по ID с полной информацией
|
||||
func (s *objectServiceImpl) GetObjectByID(ctx context.Context, id uint) (*ObjectResponse, error) {
|
||||
object, err := s.objectRepository.GetByID(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrObjectNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get object by id: %w", err)
|
||||
}
|
||||
|
||||
// Получаем дополнительные данные
|
||||
owner, _ := s.objectRepository.GetOwner(id)
|
||||
touristRating, _ := s.objectRepository.GetTouristRating(id)
|
||||
entrepreneurRating, _ := s.objectRepository.GetEntrepreneurRating(id)
|
||||
feedbacks, _ := s.objectRepository.GetFeedbacks(id, 0, 5) // Последние 5 отзывов
|
||||
|
||||
return s.mapToObjectResponse(object, owner, touristRating, entrepreneurRating, feedbacks), nil
|
||||
}
|
||||
|
||||
// CreateObject создает новый объект
|
||||
func (s *objectServiceImpl) CreateObject(ctx context.Context, req *CreateObjectRequest) (*ObjectResponse, error) {
|
||||
// Валидация
|
||||
if err := s.validateCreateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Устанавливаем значения по умолчанию
|
||||
isActive := true
|
||||
if req.IsActive != nil {
|
||||
isActive = *req.IsActive
|
||||
}
|
||||
|
||||
isVerified := false
|
||||
if req.IsVerified != nil {
|
||||
isVerified = *req.IsVerified
|
||||
}
|
||||
|
||||
object := &models.Object{
|
||||
OwnerID: req.OwnerID,
|
||||
ShortName: req.ShortName,
|
||||
LongName: req.LongName,
|
||||
Type: req.Type,
|
||||
Phone: req.Phone,
|
||||
Email: req.Email,
|
||||
Site: req.Site,
|
||||
ShortDescription: req.ShortDescription,
|
||||
Description: req.Description,
|
||||
Address: req.Address,
|
||||
Latitude: req.Latitude,
|
||||
Longitude: req.Longitude,
|
||||
IsActive: isActive,
|
||||
IsVerified: isVerified,
|
||||
FeedbackCount: 0,
|
||||
}
|
||||
|
||||
if err := s.objectRepository.Create(object); err != nil {
|
||||
return nil, fmt.Errorf("failed to create object: %w", err)
|
||||
}
|
||||
|
||||
// Создаем начальные записи рейтингов
|
||||
s.initializeRatings(object.ID)
|
||||
|
||||
return s.GetObjectByID(ctx, object.ID)
|
||||
}
|
||||
|
||||
// UpdateObject обновляет существующий объект
|
||||
func (s *objectServiceImpl) UpdateObject(ctx context.Context, id uint, req *UpdateObjectRequest) (*ObjectResponse, error) {
|
||||
// Проверяем существование объекта
|
||||
existing, err := s.objectRepository.GetByID(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrObjectNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get object: %w", err)
|
||||
}
|
||||
|
||||
// Применяем изменения
|
||||
s.applyUpdates(existing, req)
|
||||
|
||||
if err := s.objectRepository.Update(existing); err != nil {
|
||||
return nil, fmt.Errorf("failed to update object: %w", err)
|
||||
}
|
||||
|
||||
return s.GetObjectByID(ctx, id)
|
||||
}
|
||||
|
||||
// DeleteObject мягко удаляет объект
|
||||
func (s *objectServiceImpl) DeleteObject(ctx context.Context, id uint) error {
|
||||
// Проверяем существование
|
||||
if _, err := s.objectRepository.GetByID(id); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrObjectNotFound
|
||||
}
|
||||
return fmt.Errorf("failed to get object: %w", err)
|
||||
}
|
||||
|
||||
if err := s.objectRepository.Delete(id); err != nil {
|
||||
return fmt.Errorf("failed to delete object: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListObjects возвращает список объектов с пагинацией и фильтрацией
|
||||
func (s *objectServiceImpl) ListObjects(ctx context.Context, req *ListObjectsRequest) (*ObjectListResponse, error) {
|
||||
// Устанавливаем значения по умолчанию
|
||||
page := req.Page
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
pageSize := req.PageSize
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var objects []models.Object
|
||||
var total int64
|
||||
var err error
|
||||
|
||||
// Применяем фильтры
|
||||
switch {
|
||||
case req.Type != "":
|
||||
objects, err = s.objectRepository.ListByType(req.Type, offset, pageSize)
|
||||
if err == nil {
|
||||
total, _ = s.countObjectsByType(req.Type)
|
||||
}
|
||||
case req.Status != nil:
|
||||
objects, err = s.objectRepository.ListByStatus(*req.Status, offset, pageSize)
|
||||
if err == nil {
|
||||
total, _ = s.countObjectsByStatus(*req.Status)
|
||||
}
|
||||
case req.Query != "":
|
||||
objects, err = s.objectRepository.Search(req.Query, offset, pageSize)
|
||||
if err == nil {
|
||||
total, _ = s.countObjectsBySearch(req.Query)
|
||||
}
|
||||
default:
|
||||
objects, err = s.objectRepository.List(offset, pageSize)
|
||||
if err == nil {
|
||||
total, _ = s.objectRepository.Count()
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list objects: %w", err)
|
||||
}
|
||||
|
||||
items := make([]ObjectShortResponse, len(objects))
|
||||
for i, obj := range objects {
|
||||
items[i] = s.mapToObjectShortResponse(&obj)
|
||||
}
|
||||
|
||||
totalPages := int(total) / pageSize
|
||||
if int(total)%pageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &ObjectListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetObjectsByOwner возвращает объекты владельца
|
||||
func (s *objectServiceImpl) GetObjectsByOwner(ctx context.Context, ownerID uint, page, pageSize int) (*ObjectListResponse, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
objects, err := s.objectRepository.ListByOwner(ownerID, offset, pageSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get objects by owner: %w", err)
|
||||
}
|
||||
|
||||
total, _ := s.countObjectsByOwner(ownerID)
|
||||
|
||||
items := make([]ObjectShortResponse, len(objects))
|
||||
for i, obj := range objects {
|
||||
items[i] = s.mapToObjectShortResponse(&obj)
|
||||
}
|
||||
|
||||
totalPages := int(total) / pageSize
|
||||
if int(total)%pageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &ObjectListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetObjectsByType возвращает объекты по типу
|
||||
func (s *objectServiceImpl) GetObjectsByType(ctx context.Context, objectType string, page, pageSize int) (*ObjectListResponse, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
objects, err := s.objectRepository.ListByType(objectType, offset, pageSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get objects by type: %w", err)
|
||||
}
|
||||
|
||||
total, _ := s.countObjectsByType(objectType)
|
||||
|
||||
items := make([]ObjectShortResponse, len(objects))
|
||||
for i, obj := range objects {
|
||||
items[i] = s.mapToObjectShortResponse(&obj)
|
||||
}
|
||||
|
||||
totalPages := int(total) / pageSize
|
||||
if int(total)%pageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &ObjectListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SearchObjects ищет объекты по запросу
|
||||
func (s *objectServiceImpl) SearchObjects(ctx context.Context, query string, page, pageSize int) (*ObjectListResponse, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
objects, err := s.objectRepository.Search(query, offset, pageSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to search objects: %w", err)
|
||||
}
|
||||
|
||||
total, _ := s.countObjectsBySearch(query)
|
||||
|
||||
items := make([]ObjectShortResponse, len(objects))
|
||||
for i, obj := range objects {
|
||||
items[i] = s.mapToObjectShortResponse(&obj)
|
||||
}
|
||||
|
||||
totalPages := int(total) / pageSize
|
||||
if int(total)%pageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &ObjectListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetNearbyObjects возвращает объекты в радиусе
|
||||
func (s *objectServiceImpl) GetNearbyObjects(ctx context.Context, lat, lng, radius float64, page, pageSize int) (*ObjectListResponse, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
objects, err := s.objectRepository.GetNearby(lat, lng, radius, offset, pageSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get nearby objects: %w", err)
|
||||
}
|
||||
|
||||
items := make([]ObjectShortResponse, len(objects))
|
||||
for i, obj := range objects {
|
||||
items[i] = s.mapToObjectShortResponse(&obj)
|
||||
}
|
||||
|
||||
return &ObjectListResponse{
|
||||
Items: items,
|
||||
Total: int64(len(objects)),
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: 1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ToggleVerification переключает статус верификации
|
||||
func (s *objectServiceImpl) ToggleVerification(ctx context.Context, id uint, verified bool) error {
|
||||
if err := s.objectRepository.ToggleVerification(id, verified); err != nil {
|
||||
return fmt.Errorf("failed to toggle verification: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateFeedback создает отзыв
|
||||
func (s *objectServiceImpl) CreateFeedback(ctx context.Context, req *CreateFeedbackRequest, ownerID uint) (*FeedbackResponse, error) {
|
||||
// Проверяем существование объекта
|
||||
if _, err := s.objectRepository.GetByID(req.ObjectID); err != nil {
|
||||
return nil, ErrObjectNotFound
|
||||
}
|
||||
|
||||
feedback := &models.Feedback{
|
||||
OwnerID: ownerID,
|
||||
ObjectID: req.ObjectID,
|
||||
Platform: req.Platform,
|
||||
Score: req.Score,
|
||||
Text: req.Text,
|
||||
}
|
||||
|
||||
// TODO: Добавить метод CreateFeedback в репозиторий
|
||||
// if err := s.objectRepository.CreateFeedback(feedback); err != nil {
|
||||
// return nil, fmt.Errorf("failed to create feedback: %w", err)
|
||||
// }
|
||||
|
||||
// Обновляем счетчик отзывов
|
||||
if err := s.objectRepository.UpdateFeedbackCount(req.ObjectID, 1); err != nil {
|
||||
// Логируем ошибку, но не прерываем выполнение
|
||||
fmt.Printf("Failed to update feedback count: %v\n", err)
|
||||
}
|
||||
|
||||
return s.GetFeedbackByID(ctx, feedback.ID)
|
||||
}
|
||||
|
||||
// UpdateFeedback обновляет отзыв
|
||||
func (s *objectServiceImpl) UpdateFeedback(ctx context.Context, id uint, req *UpdateFeedbackRequest, ownerID uint) (*FeedbackResponse, error) {
|
||||
// TODO: Реализовать обновление отзыва
|
||||
return nil, ErrNotImplemented
|
||||
}
|
||||
|
||||
// DeleteFeedback удаляет отзыв
|
||||
func (s *objectServiceImpl) DeleteFeedback(ctx context.Context, id uint, ownerID uint) error {
|
||||
// TODO: Реализовать удаление отзыва
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// GetFeedbackByID возвращает отзыв по ID
|
||||
func (s *objectServiceImpl) GetFeedbackByID(ctx context.Context, id uint) (*FeedbackResponse, error) {
|
||||
// TODO: Добавить метод GetFeedbackByID в репозиторий
|
||||
// feedback, err := s.objectRepository.GetFeedbackByID(id)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("failed to get feedback: %w", err)
|
||||
// }
|
||||
|
||||
// return s.mapToFeedbackResponse(feedback), nil
|
||||
return nil, ErrNotImplemented
|
||||
}
|
||||
|
||||
// GetFeedbacksByObject возвращает отзывы объекта
|
||||
func (s *objectServiceImpl) GetFeedbacksByObject(ctx context.Context, objectID uint, page, pageSize int) (*FeedbackListResponse, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
feedbacks, err := s.objectRepository.GetFeedbacks(objectID, offset, pageSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get feedbacks: %w", err)
|
||||
}
|
||||
|
||||
count, err := s.objectRepository.GetFeedbackCount(objectID)
|
||||
if err != nil {
|
||||
count = 0
|
||||
}
|
||||
|
||||
items := make([]FeedbackResponse, len(feedbacks))
|
||||
for i, fb := range feedbacks {
|
||||
items[i] = *s.mapToFeedbackResponse(&fb)
|
||||
}
|
||||
|
||||
totalPages := count / pageSize
|
||||
if count%pageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &FeedbackListResponse{
|
||||
Items: items,
|
||||
Total: int64(count),
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateRatingVote создает голос в рейтинге
|
||||
func (s *objectServiceImpl) CreateRatingVote(ctx context.Context, req *CreateRatingVoteRequest, voterID uint) (*RatingVoteResponse, error) {
|
||||
// Проверяем, не голосовал ли уже пользователь
|
||||
existing, _ := s.GetUserRatingVote(ctx, req.TargetID, voterID, req.Platform)
|
||||
if existing != nil {
|
||||
return nil, ErrAlreadyVoted
|
||||
}
|
||||
|
||||
ratingVote := &models.RatingVote{
|
||||
Platform: req.Platform,
|
||||
TargetID: req.TargetID,
|
||||
VoterID: voterID,
|
||||
Score: req.Score,
|
||||
}
|
||||
|
||||
// TODO: Добавить метод CreateRatingVote в репозиторий
|
||||
// if err := s.objectRepository.CreateRatingVote(ratingVote); err != nil {
|
||||
// return nil, fmt.Errorf("failed to create rating vote: %w", err)
|
||||
// }
|
||||
|
||||
// Обновляем статистику рейтинга
|
||||
s.updateRatingStats(req.TargetID, req.Platform)
|
||||
|
||||
return &RatingVoteResponse{
|
||||
ID: ratingVote.ID,
|
||||
CreatedAt: ratingVote.CreatedAt,
|
||||
Platform: ratingVote.Platform,
|
||||
TargetID: ratingVote.TargetID,
|
||||
VoterID: ratingVote.VoterID,
|
||||
Score: ratingVote.Score,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetObjectRating возвращает рейтинг объекта
|
||||
func (s *objectServiceImpl) GetObjectRating(ctx context.Context, objectID uint, platform models.PlatformType) (*RatingResponse, error) {
|
||||
var rating *models.Rating
|
||||
var err error
|
||||
|
||||
if platform == models.PlatformTourist {
|
||||
rating, err = s.objectRepository.GetTouristRating(objectID)
|
||||
} else {
|
||||
rating, err = s.objectRepository.GetEntrepreneurRating(objectID)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get rating: %w", err)
|
||||
}
|
||||
|
||||
return s.mapToRatingResponse(rating), nil
|
||||
}
|
||||
|
||||
// GetUserRatingVote возвращает голос пользователя
|
||||
func (s *objectServiceImpl) GetUserRatingVote(ctx context.Context, objectID uint, userID uint, platform models.PlatformType) (*RatingVoteResponse, error) {
|
||||
// TODO: Добавить метод GetUserRatingVote в репозиторий
|
||||
// vote, err := s.objectRepository.GetUserRatingVote(objectID, userID, platform)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// return &RatingVoteResponse{
|
||||
// ID: vote.ID,
|
||||
// CreatedAt: vote.CreatedAt,
|
||||
// Platform: vote.Platform,
|
||||
// TargetID: vote.TargetID,
|
||||
// VoterID: vote.VoterID,
|
||||
// Score: vote.Score,
|
||||
// }, nil
|
||||
return nil, ErrNotImplemented
|
||||
}
|
||||
|
||||
// Вспомогательные методы
|
||||
|
||||
func (s *objectServiceImpl) validateCreateRequest(req *CreateObjectRequest) error {
|
||||
if req.OwnerID == 0 {
|
||||
return ErrInvalidOwnerID
|
||||
}
|
||||
if req.ShortName == "" {
|
||||
return ErrShortNameRequired
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *objectServiceImpl) applyUpdates(object *models.Object, req *UpdateObjectRequest) {
|
||||
if req.ShortName != nil {
|
||||
object.ShortName = *req.ShortName
|
||||
}
|
||||
if req.LongName != nil {
|
||||
object.LongName = *req.LongName
|
||||
}
|
||||
if req.Type != nil {
|
||||
object.Type = *req.Type
|
||||
}
|
||||
if req.Phone != nil {
|
||||
object.Phone = *req.Phone
|
||||
}
|
||||
if req.Email != nil {
|
||||
object.Email = *req.Email
|
||||
}
|
||||
if req.Site != nil {
|
||||
object.Site = *req.Site
|
||||
}
|
||||
if req.ShortDescription != nil {
|
||||
object.ShortDescription = *req.ShortDescription
|
||||
}
|
||||
if req.Description != nil {
|
||||
object.Description = *req.Description
|
||||
}
|
||||
if req.Address != nil {
|
||||
object.Address = *req.Address
|
||||
}
|
||||
if req.Latitude != nil {
|
||||
object.Latitude = *req.Latitude
|
||||
}
|
||||
if req.Longitude != nil {
|
||||
object.Longitude = *req.Longitude
|
||||
}
|
||||
if req.IsActive != nil {
|
||||
object.IsActive = *req.IsActive
|
||||
}
|
||||
if req.IsVerified != nil {
|
||||
object.IsVerified = *req.IsVerified
|
||||
}
|
||||
}
|
||||
|
||||
func (s *objectServiceImpl) initializeRatings(objectID uint) {
|
||||
// Создаем записи рейтингов для туристической и предпринимательской платформ
|
||||
// TODO: Добавить создание рейтингов в репозиторий
|
||||
}
|
||||
|
||||
func (s *objectServiceImpl) updateRatingStats(objectID uint, platform models.PlatformType) {
|
||||
// Обновляем статистику рейтинга
|
||||
// TODO: Реализовать обновление статистики
|
||||
}
|
||||
|
||||
func (s *objectServiceImpl) mapToObjectResponse(object *models.Object, owner *models.Account, touristRating, entrepreneurRating *models.Rating, feedbacks []models.Feedback) *ObjectResponse {
|
||||
resp := &ObjectResponse{
|
||||
ID: object.ID,
|
||||
CreatedAt: object.CreatedAt,
|
||||
UpdatedAt: object.UpdatedAt,
|
||||
OwnerID: object.OwnerID,
|
||||
ShortName: object.ShortName,
|
||||
LongName: object.LongName,
|
||||
Type: object.Type,
|
||||
Phone: object.Phone,
|
||||
Email: object.Email,
|
||||
Site: object.Site,
|
||||
ShortDescription: object.ShortDescription,
|
||||
Description: object.Description,
|
||||
Address: object.Address,
|
||||
Latitude: object.Latitude,
|
||||
Longitude: object.Longitude,
|
||||
IsActive: object.IsActive,
|
||||
IsVerified: object.IsVerified,
|
||||
FeedbackCount: object.FeedbackCount,
|
||||
}
|
||||
|
||||
if object.DeletedAt.Valid {
|
||||
resp.DeletedAt = &object.DeletedAt.Time
|
||||
}
|
||||
|
||||
if owner != nil {
|
||||
resp.Owner = &account.AccountResponse{
|
||||
ID: owner.ID,
|
||||
FullName: owner.FullName,
|
||||
Email: owner.Email,
|
||||
// Добавьте другие поля
|
||||
}
|
||||
}
|
||||
|
||||
if touristRating != nil {
|
||||
resp.TouristRating = s.mapToRatingResponse(touristRating)
|
||||
}
|
||||
|
||||
if entrepreneurRating != nil {
|
||||
resp.EntrepreneurRating = s.mapToRatingResponse(entrepreneurRating)
|
||||
}
|
||||
|
||||
if len(feedbacks) > 0 {
|
||||
resp.Feedbacks = make([]FeedbackShortResponse, len(feedbacks))
|
||||
for i, fb := range feedbacks {
|
||||
resp.Feedbacks[i] = FeedbackShortResponse{
|
||||
ID: fb.ID,
|
||||
CreatedAt: fb.CreatedAt,
|
||||
OwnerID: fb.OwnerID,
|
||||
Platform: fb.Platform,
|
||||
Score: fb.Score,
|
||||
Text: fb.Text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *objectServiceImpl) mapToObjectShortResponse(object *models.Object) ObjectShortResponse {
|
||||
return ObjectShortResponse{
|
||||
ID: object.ID,
|
||||
ShortName: object.ShortName,
|
||||
LongName: object.LongName,
|
||||
Type: object.Type,
|
||||
Address: object.Address,
|
||||
IsActive: object.IsActive,
|
||||
IsVerified: object.IsVerified,
|
||||
FeedbackCount: object.FeedbackCount,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *objectServiceImpl) mapToRatingResponse(rating *models.Rating) *RatingResponse {
|
||||
return &RatingResponse{
|
||||
ID: rating.ID,
|
||||
CreatedAt: rating.CreatedAt,
|
||||
UpdatedAt: rating.UpdatedAt,
|
||||
Platform: rating.Platform,
|
||||
AverageScore: rating.AverageScore,
|
||||
TotalVotes: rating.TotalVotes,
|
||||
VoteBreakdown: VoteBreakdownDTO{
|
||||
Score1: rating.VoteBreakdown.Score1,
|
||||
Score2: rating.VoteBreakdown.Score2,
|
||||
Score3: rating.VoteBreakdown.Score3,
|
||||
Score4: rating.VoteBreakdown.Score4,
|
||||
Score5: rating.VoteBreakdown.Score5,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *objectServiceImpl) mapToFeedbackResponse(feedback *models.Feedback) *FeedbackResponse {
|
||||
return &FeedbackResponse{
|
||||
ID: feedback.ID,
|
||||
CreatedAt: feedback.CreatedAt,
|
||||
UpdatedAt: feedback.UpdatedAt,
|
||||
OwnerID: feedback.OwnerID,
|
||||
ObjectID: feedback.ObjectID,
|
||||
Platform: feedback.Platform,
|
||||
Score: feedback.Score,
|
||||
Text: feedback.Text,
|
||||
CommentCount: feedback.CommentCount,
|
||||
}
|
||||
}
|
||||
|
||||
// Методы для подсчета (временные, должны быть в репозитории)
|
||||
func (s *objectServiceImpl) countObjectsByType(objectType string) (int64, error) {
|
||||
// TODO: Добавить метод CountByType в репозиторий
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (s *objectServiceImpl) countObjectsByStatus(isActive bool) (int64, error) {
|
||||
// TODO: Добавить метод CountByStatus в репозиторий
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (s *objectServiceImpl) countObjectsByOwner(ownerID uint) (int64, error) {
|
||||
// TODO: Добавить метод CountByOwner в репозиторий
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (s *objectServiceImpl) countObjectsBySearch(query string) (int64, error) {
|
||||
// TODO: Добавить метод CountBySearch в репозиторий
|
||||
return 0, nil
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package object
|
||||
|
||||
// ListObjectsRequest параметры для получения списка объектов
|
||||
type ListObjectsRequest struct {
|
||||
Page int
|
||||
PageSize int
|
||||
Type string
|
||||
Status *bool
|
||||
Query string
|
||||
}
|
||||
|
||||
// FeedbackListResponse ответ со списком отзывов
|
||||
type FeedbackListResponse struct {
|
||||
Items []FeedbackResponse `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package rating
|
||||
|
||||
import (
|
||||
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ type Rating struct {
|
||||
// Используется для отображения распределения голосов от 1 до 5
|
||||
type VoteBreakdown struct {
|
||||
// Base содержит общие поля для всех моделей
|
||||
Base Base `gorm:"embedded"`
|
||||
Base `gorm:"embedded"`
|
||||
|
||||
// RatingID - идентификатор рейтинга, к которому относится детализация
|
||||
RatingID uint `json:"rating_id"`
|
||||
@@ -61,7 +61,7 @@ type VoteBreakdown struct {
|
||||
type RatingVote struct {
|
||||
// Base содержит общие поля для всех моделей:
|
||||
// ID, CreatedAt, UpdatedAt, DeletedAt (история обновлений)
|
||||
Base Base `gorm:"embedded"`
|
||||
Base `gorm:"embedded"`
|
||||
|
||||
// Platform - платформа, на которой был сделан голос
|
||||
Platform PlatformType `json:"platform"`
|
||||
|
||||
Reference in New Issue
Block a user