Files
tp/main_dc/yalarba/api_yal/internal/domain/object/service.go
T
valitovgaziz 979c265e36 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
2026-03-31 16:53:24 +05:00

732 lines
22 KiB
Go

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
}