initial commit

This commit is contained in:
2025-11-29 00:28:21 +05:00
parent 46229acc82
commit ec3b03a935
76 changed files with 13492 additions and 0 deletions

View File

@@ -0,0 +1,202 @@
package service
import (
"context"
"fmt"
"git.kirlllll.ru/volontery/backend/internal/database"
"git.kirlllll.ru/volontery/backend/internal/repository"
"github.com/jackc/pgx/v5/pgtype"
)
// RequestService предоставляет методы для работы с заявками
type RequestService struct {
requestRepo *repository.RequestRepository
}
// NewRequestService создает новый RequestService
func NewRequestService(requestRepo *repository.RequestRepository) *RequestService {
return &RequestService{
requestRepo: requestRepo,
}
}
// CreateRequestInput - входные данные для создания заявки
type CreateRequestInput struct {
RequesterID int64 `json:"requester_id"`
RequestTypeID int64 `json:"request_type_id"`
Title string `json:"title"`
Description string `json:"description"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
Address string `json:"address"`
City string `json:"city,omitempty"`
DesiredCompletionDate *string `json:"desired_completion_date,omitempty"`
Urgency string `json:"urgency"`
ContactPhone string `json:"contact_phone,omitempty"`
ContactNotes string `json:"contact_notes,omitempty"`
}
// CreateRequest создает новую заявку
func (s *RequestService) CreateRequest(ctx context.Context, input CreateRequestInput) (*database.CreateRequestRow, error) {
// Валидация
if input.Title == "" {
return nil, fmt.Errorf("title is required")
}
if input.Description == "" {
return nil, fmt.Errorf("description is required")
}
if input.Latitude == 0 || input.Longitude == 0 {
return nil, fmt.Errorf("location is required")
}
// Создание заявки
return s.requestRepo.Create(ctx, database.CreateRequestParams{
RequesterID: input.RequesterID,
RequestTypeID: input.RequestTypeID,
Title: input.Title,
Description: input.Description,
StMakepoint: input.Longitude,
StMakepoint_2: input.Latitude,
Address: input.Address,
City: stringToPgText(input.City),
Urgency: stringToPgText(input.Urgency),
ContactPhone: stringToPgText(input.ContactPhone),
ContactNotes: stringToPgText(input.ContactNotes),
})
}
// GetRequest получает заявку по ID
func (s *RequestService) GetRequest(ctx context.Context, id int64) (*database.GetRequestByIDRow, error) {
return s.requestRepo.GetByID(ctx, id)
}
// GetUserRequests получает заявки пользователя
func (s *RequestService) GetUserRequests(ctx context.Context, userID int64, limit, offset int32) ([]database.GetRequestsByRequesterRow, error) {
return s.requestRepo.GetByRequester(ctx, database.GetRequestsByRequesterParams{
RequesterID: userID,
Limit: limit,
Offset: offset,
})
}
// FindNearbyRequests ищет заявки рядом с точкой
func (s *RequestService) FindNearbyRequests(ctx context.Context, lat, lon float64, radiusMeters float64, statuses []database.RequestStatus, limit, offset int32) ([]database.FindRequestsNearbyRow, error) {
// Конвертируем []RequestStatus в []string
statusStrings := make([]string, len(statuses))
for i, status := range statuses {
statusStrings[i] = string(status)
}
return s.requestRepo.FindNearby(ctx, database.FindRequestsNearbyParams{
StMakepoint: lon,
StMakepoint_2: lat,
Column3: statusStrings,
StDwithin: radiusMeters,
Limit: limit,
Offset: offset,
})
}
// FindRequestsInBounds ищет заявки в прямоугольной области (для карты)
func (s *RequestService) FindRequestsInBounds(ctx context.Context, statuses []database.RequestStatus, minLon, minLat, maxLon, maxLat float64) ([]database.FindRequestsInBoundsRow, error) {
// Конвертируем []RequestStatus в []string
statusStrings := make([]string, len(statuses))
for i, status := range statuses {
statusStrings[i] = string(status)
}
return s.requestRepo.FindInBounds(ctx, database.FindRequestsInBoundsParams{
Column1: statusStrings,
StMakeenvelope: minLon,
StMakeenvelope_2: minLat,
StMakeenvelope_3: maxLon,
StMakeenvelope_4: maxLat,
})
}
// CreateVolunteerResponse создает отклик волонтера на заявку
func (s *RequestService) CreateVolunteerResponse(ctx context.Context, requestID, volunteerID int64, message string) (*database.VolunteerResponse, error) {
return s.requestRepo.CreateVolunteerResponse(ctx, database.CreateVolunteerResponseParams{
RequestID: requestID,
VolunteerID: volunteerID,
Message: stringToPgText(message),
})
}
// GetRequestResponses получает отклики на заявку
func (s *RequestService) GetRequestResponses(ctx context.Context, requestID int64) ([]database.GetResponsesByRequestRow, error) {
return s.requestRepo.GetResponsesByRequest(ctx, requestID)
}
// ListRequestTypes получает список типов заявок
func (s *RequestService) ListRequestTypes(ctx context.Context) ([]database.RequestType, error) {
return s.requestRepo.ListTypes(ctx)
}
// GetPendingModerationRequests получает заявки на модерации
func (s *RequestService) GetPendingModerationRequests(ctx context.Context, limit, offset int32) ([]database.GetPendingModerationRequestsRow, error) {
return s.requestRepo.GetPendingModerationRequests(ctx, limit, offset)
}
// ApproveRequest одобряет заявку
func (s *RequestService) ApproveRequest(ctx context.Context, requestID, moderatorID int64, comment *string) error {
moderationComment := stringToPgText("")
if comment != nil {
moderationComment = stringToPgText(*comment)
}
return s.requestRepo.ApproveRequest(ctx, database.ApproveRequestParams{
ID: requestID,
ModeratedBy: pgtype.Int8{
Int64: moderatorID,
Valid: true,
},
ModerationComment: moderationComment,
})
}
// RejectRequest отклоняет заявку
func (s *RequestService) RejectRequest(ctx context.Context, requestID, moderatorID int64, comment string) error {
if comment == "" {
return fmt.Errorf("rejection comment is required")
}
return s.requestRepo.RejectRequest(ctx, database.RejectRequestParams{
ID: requestID,
ModeratedBy: pgtype.Int8{
Int64: moderatorID,
Valid: true,
},
ModerationComment: stringToPgText(comment),
})
}
// GetModeratedRequests получает заявки, модерированные указанным модератором
func (s *RequestService) GetModeratedRequests(ctx context.Context, moderatorID int64, limit, offset int32) ([]database.GetModeratedRequestsRow, error) {
return s.requestRepo.GetModeratedRequests(ctx, moderatorID, limit, offset)
}
// AcceptVolunteerResponse принимает отклик волонтера через хранимую процедуру
func (s *RequestService) AcceptVolunteerResponse(ctx context.Context, responseID, requesterID int64) (*database.CallAcceptVolunteerResponseRow, error) {
return s.requestRepo.AcceptVolunteerResponse(ctx, responseID, requesterID)
}
// CompleteRequestWithRating завершает заявку с рейтингом через хранимую процедуру
func (s *RequestService) CompleteRequestWithRating(ctx context.Context, requestID, requesterID int64, rating int32, comment *string) (*database.CallCompleteRequestWithRatingRow, error) {
if rating < 1 || rating > 5 {
return nil, fmt.Errorf("rating must be between 1 and 5")
}
return s.requestRepo.CompleteRequestWithRating(ctx, requestID, requesterID, rating, comment)
}
// ModerateRequestProcedure модерирует заявку через хранимую процедуру
func (s *RequestService) ModerateRequestProcedure(ctx context.Context, requestID, moderatorID int64, action string, comment *string) (*database.CallModerateRequestRow, error) {
if action != "approve" && action != "reject" {
return nil, fmt.Errorf("action must be 'approve' or 'reject'")
}
if action == "reject" && (comment == nil || *comment == "") {
return nil, fmt.Errorf("comment is required when rejecting")
}
return s.requestRepo.ModerateRequestProcedure(ctx, requestID, moderatorID, action, comment)
}