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,460 @@
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
}