Files
backend/internal/api/handlers/requests.go
2025-12-13 22:34:01 +05:00

461 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handlers
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"git.kirlllll.ru/volontery/backend/internal/api/middleware"
"git.kirlllll.ru/volontery/backend/internal/database"
"git.kirlllll.ru/volontery/backend/internal/service"
"github.com/go-chi/chi/v5"
)
// RequestHandler обрабатывает запросы заявок
type RequestHandler struct {
requestService *service.RequestService
}
// NewRequestHandler создает новый RequestHandler
func NewRequestHandler(requestService *service.RequestService) *RequestHandler {
return &RequestHandler{
requestService: requestService,
}
}
// CreateRequest создает новую заявку
// POST /api/v1/requests
func (h *RequestHandler) CreateRequest(w http.ResponseWriter, r *http.Request) {
userID, ok := middleware.GetUserIDFromContext(r.Context())
if !ok {
respondError(w, http.StatusUnauthorized, "unauthorized")
return
}
var input service.CreateRequestInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
respondError(w, http.StatusBadRequest, "invalid request body")
return
}
input.RequesterID = userID
request, err := h.requestService.CreateRequest(r.Context(), input)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
return
}
respondJSON(w, http.StatusCreated, request)
}
// GetRequest получает заявку по ID
// GET /api/v1/requests/{id}
func (h *RequestHandler) GetRequest(w http.ResponseWriter, r *http.Request) {
idStr := chi.URLParam(r, "id")
requestID, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
respondError(w, http.StatusBadRequest, "invalid request id")
return
}
request, err := h.requestService.GetRequest(r.Context(), requestID)
if err != nil {
respondError(w, http.StatusNotFound, "request not found")
return
}
respondJSON(w, http.StatusOK, request)
}
// GetMyRequests получает заявки текущего пользователя
// GET /api/v1/requests/my
func (h *RequestHandler) GetMyRequests(w http.ResponseWriter, r *http.Request) {
userID, ok := middleware.GetUserIDFromContext(r.Context())
if !ok {
respondError(w, http.StatusUnauthorized, "unauthorized")
return
}
limit, offset := parsePagination(r)
requests, err := h.requestService.GetUserRequests(r.Context(), userID, limit, offset)
if err != nil {
respondError(w, http.StatusInternalServerError, "failed to get requests")
return
}
respondJSON(w, http.StatusOK, requests)
}
// FindNearbyRequests ищет заявки рядом с точкой
// GET /api/v1/requests/nearby
func (h *RequestHandler) FindNearbyRequests(w http.ResponseWriter, r *http.Request) {
latStr := r.URL.Query().Get("lat")
lonStr := r.URL.Query().Get("lon")
radiusStr := r.URL.Query().Get("radius")
if latStr == "" || lonStr == "" {
respondError(w, http.StatusBadRequest, "lat and lon are required")
return
}
lat, err := strconv.ParseFloat(latStr, 64)
if err != nil {
respondError(w, http.StatusBadRequest, "invalid latitude")
return
}
lon, err := strconv.ParseFloat(lonStr, 64)
if err != nil {
respondError(w, http.StatusBadRequest, "invalid longitude")
return
}
radius := 5000.0 // default 5km
if radiusStr != "" {
radius, err = strconv.ParseFloat(radiusStr, 64)
if err != nil {
respondError(w, http.StatusBadRequest, "invalid radius")
return
}
}
limit, offset := parsePagination(r)
// По умолчанию ищем только активные заявки
statuses := []database.RequestStatus{
database.RequestStatusPendingModeration,
database.RequestStatusApproved,
database.RequestStatusInProgress,
}
requests, err := h.requestService.FindNearbyRequests(r.Context(), lat, lon, radius, statuses, limit, offset)
if err != nil {
respondError(w, http.StatusInternalServerError, fmt.Sprintf("failed to find requests: %v", err))
return
}
respondJSON(w, http.StatusOK, requests)
}
// FindRequestsInBounds ищет заявки в прямоугольной области
// GET /api/v1/requests/bounds
func (h *RequestHandler) FindRequestsInBounds(w http.ResponseWriter, r *http.Request) {
minLonStr := r.URL.Query().Get("min_lon")
minLatStr := r.URL.Query().Get("min_lat")
maxLonStr := r.URL.Query().Get("max_lon")
maxLatStr := r.URL.Query().Get("max_lat")
if minLonStr == "" || minLatStr == "" || maxLonStr == "" || maxLatStr == "" {
respondError(w, http.StatusBadRequest, "min_lon, min_lat, max_lon, max_lat are required")
return
}
minLon, err := strconv.ParseFloat(minLonStr, 64)
if err != nil {
respondError(w, http.StatusBadRequest, "invalid min_lon")
return
}
minLat, err := strconv.ParseFloat(minLatStr, 64)
if err != nil {
respondError(w, http.StatusBadRequest, "invalid min_lat")
return
}
maxLon, err := strconv.ParseFloat(maxLonStr, 64)
if err != nil {
respondError(w, http.StatusBadRequest, "invalid max_lon")
return
}
maxLat, err := strconv.ParseFloat(maxLatStr, 64)
if err != nil {
respondError(w, http.StatusBadRequest, "invalid max_lat")
return
}
// По умолчанию ищем только активные заявки
statuses := []database.RequestStatus{
database.RequestStatusPendingModeration,
database.RequestStatusApproved,
database.RequestStatusInProgress,
}
requests, err := h.requestService.FindRequestsInBounds(r.Context(), statuses, minLon, minLat, maxLon, maxLat)
if err != nil {
respondError(w, http.StatusInternalServerError, fmt.Sprintf("failed to find requests: %v", err))
return
}
respondJSON(w, http.StatusOK, requests)
}
// CreateVolunteerResponse создает отклик волонтера на заявку
// POST /api/v1/requests/{id}/responses
func (h *RequestHandler) CreateVolunteerResponse(w http.ResponseWriter, r *http.Request) {
userID, ok := middleware.GetUserIDFromContext(r.Context())
if !ok {
respondError(w, http.StatusUnauthorized, "unauthorized")
return
}
idStr := chi.URLParam(r, "id")
requestID, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
respondError(w, http.StatusBadRequest, "invalid request id")
return
}
var input struct {
Message string `json:"message"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
respondError(w, http.StatusBadRequest, "invalid request body")
return
}
response, err := h.requestService.CreateVolunteerResponse(r.Context(), requestID, userID, input.Message)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
return
}
respondJSON(w, http.StatusCreated, response)
}
// GetRequestResponses получает отклики на заявку
// GET /api/v1/requests/{id}/responses
func (h *RequestHandler) GetRequestResponses(w http.ResponseWriter, r *http.Request) {
idStr := chi.URLParam(r, "id")
requestID, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
respondError(w, http.StatusBadRequest, "invalid request id")
return
}
responses, err := h.requestService.GetRequestResponses(r.Context(), requestID)
if err != nil {
respondError(w, http.StatusInternalServerError, "failed to get responses")
return
}
respondJSON(w, http.StatusOK, responses)
}
// ListRequestTypes получает список типов заявок
// GET /api/v1/request-types
func (h *RequestHandler) ListRequestTypes(w http.ResponseWriter, r *http.Request) {
types, err := h.requestService.ListRequestTypes(r.Context())
if err != nil {
respondError(w, http.StatusInternalServerError, "failed to get request types")
return
}
respondJSON(w, http.StatusOK, types)
}
// GetPendingModerationRequests получает заявки на модерации
// GET /api/v1/moderation/requests/pending
func (h *RequestHandler) GetPendingModerationRequests(w http.ResponseWriter, r *http.Request) {
limit, offset := parsePagination(r)
requests, err := h.requestService.GetPendingModerationRequests(r.Context(), limit, offset)
if err != nil {
respondError(w, http.StatusInternalServerError, "failed to get pending requests")
return
}
respondJSON(w, http.StatusOK, requests)
}
// ApproveRequest одобряет заявку
// POST /api/v1/moderation/requests/{id}/approve
func (h *RequestHandler) ApproveRequest(w http.ResponseWriter, r *http.Request) {
moderatorID, ok := middleware.GetUserIDFromContext(r.Context())
if !ok {
respondError(w, http.StatusUnauthorized, "unauthorized")
return
}
idStr := chi.URLParam(r, "id")
requestID, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
respondError(w, http.StatusBadRequest, "invalid request id")
return
}
var input struct {
Comment *string `json:"comment"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
respondError(w, http.StatusBadRequest, "invalid request body")
return
}
if err := h.requestService.ApproveRequest(r.Context(), requestID, moderatorID, input.Comment); err != nil {
respondError(w, http.StatusBadRequest, err.Error())
return
}
respondJSON(w, http.StatusOK, map[string]string{"status": "approved"})
}
// RejectRequest отклоняет заявку
// POST /api/v1/moderation/requests/{id}/reject
func (h *RequestHandler) RejectRequest(w http.ResponseWriter, r *http.Request) {
moderatorID, ok := middleware.GetUserIDFromContext(r.Context())
if !ok {
respondError(w, http.StatusUnauthorized, "unauthorized")
return
}
idStr := chi.URLParam(r, "id")
requestID, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
respondError(w, http.StatusBadRequest, "invalid request id")
return
}
var input struct {
Comment string `json:"comment"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
respondError(w, http.StatusBadRequest, "invalid request body")
return
}
if err := h.requestService.RejectRequest(r.Context(), requestID, moderatorID, input.Comment); err != nil {
respondError(w, http.StatusBadRequest, err.Error())
return
}
respondJSON(w, http.StatusOK, map[string]string{"status": "rejected"})
}
// GetMyModeratedRequests получает заявки, модерированные текущим модератором
// GET /api/v1/moderation/requests/my
func (h *RequestHandler) GetMyModeratedRequests(w http.ResponseWriter, r *http.Request) {
moderatorID, ok := middleware.GetUserIDFromContext(r.Context())
if !ok {
respondError(w, http.StatusUnauthorized, "unauthorized")
return
}
limit, offset := parsePagination(r)
requests, err := h.requestService.GetModeratedRequests(r.Context(), moderatorID, limit, offset)
if err != nil {
respondError(w, http.StatusInternalServerError, "failed to get moderated requests")
return
}
respondJSON(w, http.StatusOK, requests)
}
// AcceptVolunteerResponseHandler принимает отклик волонтера
// POST /api/v1/requests/{id}/responses/{response_id}/accept
func (h *RequestHandler) AcceptVolunteerResponseHandler(w http.ResponseWriter, r *http.Request) {
requesterID, ok := middleware.GetUserIDFromContext(r.Context())
if !ok {
respondError(w, http.StatusUnauthorized, "unauthorized")
return
}
responseIDStr := chi.URLParam(r, "response_id")
responseID, err := strconv.ParseInt(responseIDStr, 10, 64)
if err != nil {
respondError(w, http.StatusBadRequest, "invalid response id")
return
}
result, err := h.requestService.AcceptVolunteerResponse(r.Context(), responseID, requesterID)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
return
}
if !result.Success {
respondError(w, http.StatusBadRequest, result.Message)
return
}
respondJSON(w, http.StatusOK, map[string]interface{}{
"success": result.Success,
"message": result.Message,
"request_id": result.RequestID,
"volunteer_id": result.VolunteerID,
})
}
// CompleteRequestWithRatingHandler завершает заявку с рейтингом
// POST /api/v1/requests/{id}/complete
func (h *RequestHandler) CompleteRequestWithRatingHandler(w http.ResponseWriter, r *http.Request) {
requesterID, ok := middleware.GetUserIDFromContext(r.Context())
if !ok {
respondError(w, http.StatusUnauthorized, "unauthorized")
return
}
idStr := chi.URLParam(r, "id")
requestID, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
respondError(w, http.StatusBadRequest, "invalid request id")
return
}
var input struct {
Rating int32 `json:"rating"`
Comment *string `json:"comment"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
respondError(w, http.StatusBadRequest, "invalid request body")
return
}
result, err := h.requestService.CompleteRequestWithRating(r.Context(), requestID, requesterID, input.Rating, input.Comment)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
return
}
if !result.Success {
respondError(w, http.StatusBadRequest, result.Message)
return
}
respondJSON(w, http.StatusOK, map[string]interface{}{
"success": result.Success,
"message": result.Message,
"rating_id": result.RatingID,
})
}
// parsePagination парсит параметры пагинации из запроса
func parsePagination(r *http.Request) (limit, offset int32) {
limitStr := r.URL.Query().Get("limit")
offsetStr := r.URL.Query().Get("offset")
limit = 20 // default
if limitStr != "" {
if l, err := strconv.ParseInt(limitStr, 10, 32); err == nil && l > 0 && l <= 100 {
limit = int32(l)
}
}
offset = 0
if offsetStr != "" {
if o, err := strconv.ParseInt(offsetStr, 10, 32); err == nil && o >= 0 {
offset = int32(o)
}
}
return limit, offset
}