461 lines
13 KiB
Go
461 lines
13 KiB
Go
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
|
||
}
|