initial commit
This commit is contained in:
117
internal/api/handlers/auth.go
Normal file
117
internal/api/handlers/auth.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"git.kirlllll.ru/volontery/backend/internal/api/middleware"
|
||||
"git.kirlllll.ru/volontery/backend/internal/service"
|
||||
)
|
||||
|
||||
// AuthHandler обрабатывает запросы аутентификации
|
||||
type AuthHandler struct {
|
||||
authService *service.AuthService
|
||||
}
|
||||
|
||||
// NewAuthHandler создает новый AuthHandler
|
||||
func NewAuthHandler(authService *service.AuthService) *AuthHandler {
|
||||
return &AuthHandler{
|
||||
authService: authService,
|
||||
}
|
||||
}
|
||||
|
||||
// Register обрабатывает регистрацию пользователя
|
||||
// POST /api/v1/auth/register
|
||||
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.RegisterRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.authService.Register(r.Context(), req)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusCreated, resp)
|
||||
}
|
||||
|
||||
// Login обрабатывает вход пользователя
|
||||
// POST /api/v1/auth/login
|
||||
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.LoginRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.authService.Login(r.Context(), req)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusUnauthorized, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// RefreshToken обрабатывает обновление токенов
|
||||
// POST /api/v1/auth/refresh
|
||||
func (h *AuthHandler) RefreshToken(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if req.RefreshToken == "" {
|
||||
respondError(w, http.StatusBadRequest, "refresh_token is required")
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.authService.RefreshTokens(r.Context(), req.RefreshToken)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusUnauthorized, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// Logout обрабатывает выход пользователя
|
||||
// POST /api/v1/auth/logout
|
||||
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := middleware.GetUserIDFromContext(r.Context())
|
||||
if !ok {
|
||||
respondError(w, http.StatusUnauthorized, "unauthorized")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.authService.Logout(r.Context(), userID); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to logout")
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]string{"message": "logged out successfully"})
|
||||
}
|
||||
|
||||
// Me возвращает информацию о текущем пользователе
|
||||
// GET /api/v1/auth/me
|
||||
func (h *AuthHandler) Me(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := middleware.GetUserIDFromContext(r.Context())
|
||||
if !ok {
|
||||
respondError(w, http.StatusUnauthorized, "unauthorized")
|
||||
return
|
||||
}
|
||||
|
||||
email, _ := middleware.GetUserEmailFromContext(r.Context())
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"id": userID,
|
||||
"email": email,
|
||||
})
|
||||
}
|
||||
28
internal/api/handlers/helpers.go
Normal file
28
internal/api/handlers/helpers.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ErrorResponse представляет ответ с ошибкой
|
||||
type ErrorResponse struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// respondJSON отправляет JSON ответ
|
||||
func respondJSON(w http.ResponseWriter, statusCode int, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
|
||||
if data != nil {
|
||||
if err := json.NewEncoder(w).Encode(data); err != nil {
|
||||
http.Error(w, "failed to encode response", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// respondError отправляет ошибку в формате JSON
|
||||
func respondError(w http.ResponseWriter, statusCode int, message string) {
|
||||
respondJSON(w, statusCode, ErrorResponse{Error: message})
|
||||
}
|
||||
460
internal/api/handlers/requests.go
Normal file
460
internal/api/handlers/requests.go
Normal 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
|
||||
}
|
||||
308
internal/api/handlers/users.go
Normal file
308
internal/api/handlers/users.go
Normal file
@@ -0,0 +1,308 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"git.kirlllll.ru/volontery/backend/internal/api/middleware"
|
||||
"git.kirlllll.ru/volontery/backend/internal/service"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// UserHandler обрабатывает запросы пользователей
|
||||
type UserHandler struct {
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
// NewUserHandler создает новый UserHandler
|
||||
func NewUserHandler(userService *service.UserService) *UserHandler {
|
||||
return &UserHandler{
|
||||
userService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetProfile возвращает профиль пользователя
|
||||
// GET /api/v1/users/{id}
|
||||
func (h *UserHandler) GetProfile(w http.ResponseWriter, r *http.Request) {
|
||||
idStr := chi.URLParam(r, "id")
|
||||
userID, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadRequest, "invalid user id")
|
||||
return
|
||||
}
|
||||
|
||||
profile, err := h.userService.GetUserProfile(r.Context(), userID)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusNotFound, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, profile)
|
||||
}
|
||||
|
||||
// GetMyProfile возвращает профиль текущего пользователя
|
||||
// GET /api/v1/users/me
|
||||
func (h *UserHandler) GetMyProfile(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := middleware.GetUserIDFromContext(r.Context())
|
||||
if !ok {
|
||||
respondError(w, http.StatusUnauthorized, "unauthorized")
|
||||
return
|
||||
}
|
||||
|
||||
profile, err := h.userService.GetUserProfile(r.Context(), userID)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusNotFound, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, profile)
|
||||
}
|
||||
|
||||
// UpdateProfile обновляет профиль текущего пользователя
|
||||
// PATCH /api/v1/users/me
|
||||
func (h *UserHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := middleware.GetUserIDFromContext(r.Context())
|
||||
if !ok {
|
||||
respondError(w, http.StatusUnauthorized, "unauthorized")
|
||||
return
|
||||
}
|
||||
|
||||
var input service.UpdateProfileInput
|
||||
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
||||
respondError(w, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.userService.UpdateUserProfile(r.Context(), userID, input); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to update profile")
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]string{"message": "profile updated successfully"})
|
||||
}
|
||||
|
||||
// UpdateLocation обновляет местоположение пользователя
|
||||
// POST /api/v1/users/me/location
|
||||
func (h *UserHandler) UpdateLocation(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := middleware.GetUserIDFromContext(r.Context())
|
||||
if !ok {
|
||||
respondError(w, http.StatusUnauthorized, "unauthorized")
|
||||
return
|
||||
}
|
||||
|
||||
var input struct {
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
||||
respondError(w, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.userService.UpdateUserLocation(r.Context(), userID, input.Latitude, input.Longitude); err != nil {
|
||||
respondError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]string{"message": "location updated successfully"})
|
||||
}
|
||||
|
||||
// GetMyRoles возвращает роли текущего пользователя
|
||||
// GET /api/v1/users/me/roles
|
||||
func (h *UserHandler) GetMyRoles(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := middleware.GetUserIDFromContext(r.Context())
|
||||
if !ok {
|
||||
respondError(w, http.StatusUnauthorized, "unauthorized")
|
||||
return
|
||||
}
|
||||
|
||||
roles, err := h.userService.GetUserRoles(r.Context(), userID)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to get roles")
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, roles)
|
||||
}
|
||||
|
||||
// GetMyPermissions возвращает разрешения текущего пользователя
|
||||
// GET /api/v1/users/me/permissions
|
||||
func (h *UserHandler) GetMyPermissions(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := middleware.GetUserIDFromContext(r.Context())
|
||||
if !ok {
|
||||
respondError(w, http.StatusUnauthorized, "unauthorized")
|
||||
return
|
||||
}
|
||||
|
||||
permissions, err := h.userService.GetUserPermissions(r.Context(), userID)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to get permissions")
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, permissions)
|
||||
}
|
||||
|
||||
// VerifyEmail подтверждает email пользователя
|
||||
// POST /api/v1/users/me/verify-email
|
||||
func (h *UserHandler) VerifyEmail(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := middleware.GetUserIDFromContext(r.Context())
|
||||
if !ok {
|
||||
respondError(w, http.StatusUnauthorized, "unauthorized")
|
||||
return
|
||||
}
|
||||
|
||||
// В реальной системе здесь должна быть проверка токена из письма
|
||||
// Сейчас просто подтверждаем email для текущего пользователя
|
||||
if err := h.userService.VerifyEmail(r.Context(), userID); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to verify email")
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]string{"message": "email verified successfully"})
|
||||
}
|
||||
|
||||
// CheckPermission проверяет наличие разрешения у пользователя
|
||||
// GET /api/v1/users/me/permissions/{permission_name}/check
|
||||
func (h *UserHandler) CheckPermission(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := middleware.GetUserIDFromContext(r.Context())
|
||||
if !ok {
|
||||
respondError(w, http.StatusUnauthorized, "unauthorized")
|
||||
return
|
||||
}
|
||||
|
||||
permissionName := chi.URLParam(r, "permission_name")
|
||||
if permissionName == "" {
|
||||
respondError(w, http.StatusBadRequest, "permission_name is required")
|
||||
return
|
||||
}
|
||||
|
||||
hasPermission, err := h.userService.HasPermission(r.Context(), userID, permissionName)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to check permission")
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"has_permission": hasPermission,
|
||||
"permission_name": permissionName,
|
||||
})
|
||||
}
|
||||
|
||||
// ========== AdminHandler - новый хендлер для административных функций ==========
|
||||
|
||||
// AdminHandler обрабатывает административные запросы
|
||||
type AdminHandler struct {
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
// NewAdminHandler создает новый AdminHandler
|
||||
func NewAdminHandler(userService *service.UserService) *AdminHandler {
|
||||
return &AdminHandler{
|
||||
userService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
// AssignRole назначает роль пользователю
|
||||
// POST /api/v1/admin/users/{user_id}/roles/{role_id}
|
||||
func (h *AdminHandler) AssignRole(w http.ResponseWriter, r *http.Request) {
|
||||
adminID, ok := middleware.GetUserIDFromContext(r.Context())
|
||||
if !ok {
|
||||
respondError(w, http.StatusUnauthorized, "unauthorized")
|
||||
return
|
||||
}
|
||||
|
||||
// Проверка, что текущий пользователь является администратором
|
||||
// В реальной системе это должно быть в middleware
|
||||
isAdmin, err := h.userService.HasPermission(r.Context(), adminID, "manage_users")
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to check permissions")
|
||||
return
|
||||
}
|
||||
if !isAdmin {
|
||||
respondError(w, http.StatusForbidden, "admin role required")
|
||||
return
|
||||
}
|
||||
|
||||
userIDStr := chi.URLParam(r, "user_id")
|
||||
userID, err := strconv.ParseInt(userIDStr, 10, 64)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadRequest, "invalid user_id")
|
||||
return
|
||||
}
|
||||
|
||||
roleIDStr := chi.URLParam(r, "role_id")
|
||||
roleID, err := strconv.ParseInt(roleIDStr, 10, 64)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadRequest, "invalid role_id")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.userService.AssignRole(r.Context(), userID, roleID, adminID); err != nil {
|
||||
respondError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"message": "role assigned successfully",
|
||||
"user_id": userID,
|
||||
"role_id": roleID,
|
||||
})
|
||||
}
|
||||
|
||||
// ========== RequestHandler - дополнительный метод ==========
|
||||
|
||||
// ModerateRequestProcedure модерирует заявку через stored procedure
|
||||
// POST /api/v1/moderation/requests/{id}/moderate
|
||||
func (h *RequestHandler) ModerateRequestProcedure(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 {
|
||||
Action string `json:"action"`
|
||||
Comment *string `json:"comment"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
||||
respondError(w, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if input.Action != "approve" && input.Action != "reject" {
|
||||
respondError(w, http.StatusBadRequest, "action must be 'approve' or 'reject'")
|
||||
return
|
||||
}
|
||||
|
||||
if input.Action == "reject" && (input.Comment == nil || *input.Comment == "") {
|
||||
respondError(w, http.StatusBadRequest, "comment is required when rejecting")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.requestService.ModerateRequestProcedure(r.Context(), requestID, moderatorID, input.Action, 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,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user