90a96b4125
- Remove api_es service, Dockerfile, all Go source files - Remove api_es from docker-compose.yml, nginx-ssl.conf, .env, Makefile - Replace nginx /api/ proxy with /api/v1/ → api_yal:8787 - Add amenity/upload domains, AuthResponse, GET /auth/me, GET /objects/my to api_yal - Rewrite easysite frontend: types, composables, and all 5 pages to use api_yal DTOs - Wire nuxt.config public.apiBase, add useObjects CRUD composable - Update docs references from api_es to api_yal
317 lines
8.6 KiB
Go
317 lines
8.6 KiB
Go
package object
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"api_yal/internal/middleware"
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
type ObjectHandler struct {
|
|
objectService ObjectService
|
|
}
|
|
|
|
func NewObjectHandler(objectService ObjectService) *ObjectHandler {
|
|
return &ObjectHandler{
|
|
objectService: objectService,
|
|
}
|
|
}
|
|
|
|
// GetObjectByID обрабатывает GET /objects/{id}
|
|
func (h *ObjectHandler) GetObjectByID(w http.ResponseWriter, r *http.Request) {
|
|
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
|
if err != nil {
|
|
http.Error(w, "Invalid object ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
response, err := h.objectService.GetObjectByID(r.Context(), uint(id))
|
|
if err != nil {
|
|
h.handleError(w, err)
|
|
return
|
|
}
|
|
|
|
h.respondWithJSON(w, http.StatusOK, response)
|
|
}
|
|
|
|
// CreateObject обрабатывает POST /objects
|
|
func (h *ObjectHandler) CreateObject(w http.ResponseWriter, r *http.Request) {
|
|
var req CreateObjectRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Получаем ID пользователя из контекста (из AuthMiddleware)
|
|
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
|
|
if !ok {
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// Устанавливаем owner_id из аутентифицированного пользователя
|
|
req.OwnerID = userID
|
|
|
|
response, err := h.objectService.CreateObject(r.Context(), &req)
|
|
if err != nil {
|
|
h.handleError(w, err)
|
|
return
|
|
}
|
|
|
|
h.respondWithJSON(w, http.StatusCreated, response)
|
|
}
|
|
|
|
// UpdateObject обрабатывает PUT /objects/{id}
|
|
func (h *ObjectHandler) UpdateObject(w http.ResponseWriter, r *http.Request) {
|
|
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
|
if err != nil {
|
|
http.Error(w, "Invalid object ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var req UpdateObjectRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
response, err := h.objectService.UpdateObject(r.Context(), uint(id), &req)
|
|
if err != nil {
|
|
h.handleError(w, err)
|
|
return
|
|
}
|
|
|
|
h.respondWithJSON(w, http.StatusOK, response)
|
|
}
|
|
|
|
// DeleteObject обрабатывает DELETE /objects/{id}
|
|
func (h *ObjectHandler) DeleteObject(w http.ResponseWriter, r *http.Request) {
|
|
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
|
if err != nil {
|
|
http.Error(w, "Invalid object ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if err := h.objectService.DeleteObject(r.Context(), uint(id)); err != nil {
|
|
h.handleError(w, err)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
// ListObjects обрабатывает GET /objects
|
|
func (h *ObjectHandler) ListObjects(w http.ResponseWriter, r *http.Request) {
|
|
req := &ListObjectsRequest{
|
|
Page: h.getQueryParamInt(r, "page", 1),
|
|
PageSize: h.getQueryParamInt(r, "page_size", 10),
|
|
Type: r.URL.Query().Get("type"),
|
|
Query: r.URL.Query().Get("q"),
|
|
ObjectStatus: r.URL.Query().Get("status"),
|
|
}
|
|
|
|
if statusStr := r.URL.Query().Get("is_active"); statusStr != "" {
|
|
isActive, err := strconv.ParseBool(statusStr)
|
|
if err == nil {
|
|
req.Status = &isActive
|
|
}
|
|
}
|
|
|
|
response, err := h.objectService.ListObjects(r.Context(), req)
|
|
if err != nil {
|
|
h.handleError(w, err)
|
|
return
|
|
}
|
|
|
|
h.respondWithJSON(w, http.StatusOK, response)
|
|
}
|
|
|
|
// GetMyObjects обрабатывает GET /objects/my
|
|
func (h *ObjectHandler) GetMyObjects(w http.ResponseWriter, r *http.Request) {
|
|
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
|
|
if !ok {
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
page := h.getQueryParamInt(r, "page", 1)
|
|
pageSize := h.getQueryParamInt(r, "page_size", 10)
|
|
|
|
response, err := h.objectService.GetObjectsByOwner(r.Context(), userID, page, pageSize)
|
|
if err != nil {
|
|
h.handleError(w, err)
|
|
return
|
|
}
|
|
|
|
h.respondWithJSON(w, http.StatusOK, response)
|
|
}
|
|
|
|
// GetObjectsByOwner обрабатывает GET /objects/owner/{ownerId}
|
|
func (h *ObjectHandler) GetObjectsByOwner(w http.ResponseWriter, r *http.Request) {
|
|
ownerID, err := strconv.ParseUint(chi.URLParam(r, "ownerId"), 10, 32)
|
|
if err != nil {
|
|
http.Error(w, "Invalid owner ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
page := h.getQueryParamInt(r, "page", 1)
|
|
pageSize := h.getQueryParamInt(r, "page_size", 10)
|
|
|
|
response, err := h.objectService.GetObjectsByOwner(r.Context(), uint(ownerID), page, pageSize)
|
|
if err != nil {
|
|
h.handleError(w, err)
|
|
return
|
|
}
|
|
|
|
h.respondWithJSON(w, http.StatusOK, response)
|
|
}
|
|
|
|
// SearchObjects обрабатывает GET /objects/search
|
|
func (h *ObjectHandler) SearchObjects(w http.ResponseWriter, r *http.Request) {
|
|
query := r.URL.Query().Get("q")
|
|
if query == "" {
|
|
http.Error(w, "Search query is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
page := h.getQueryParamInt(r, "page", 1)
|
|
pageSize := h.getQueryParamInt(r, "page_size", 10)
|
|
|
|
response, err := h.objectService.SearchObjects(r.Context(), query, page, pageSize)
|
|
if err != nil {
|
|
h.handleError(w, err)
|
|
return
|
|
}
|
|
|
|
h.respondWithJSON(w, http.StatusOK, response)
|
|
}
|
|
|
|
// GetNearbyObjects обрабатывает GET /objects/nearby
|
|
func (h *ObjectHandler) GetNearbyObjects(w http.ResponseWriter, r *http.Request) {
|
|
lat, err := strconv.ParseFloat(r.URL.Query().Get("lat"), 64)
|
|
if err != nil {
|
|
http.Error(w, "Invalid latitude", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
lng, err := strconv.ParseFloat(r.URL.Query().Get("lng"), 64)
|
|
if err != nil {
|
|
http.Error(w, "Invalid longitude", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
radius, err := strconv.ParseFloat(r.URL.Query().Get("radius"), 64)
|
|
if err != nil || radius <= 0 {
|
|
radius = 1000 // По умолчанию 1 км
|
|
}
|
|
|
|
page := h.getQueryParamInt(r, "page", 1)
|
|
pageSize := h.getQueryParamInt(r, "page_size", 10)
|
|
|
|
response, err := h.objectService.GetNearbyObjects(r.Context(), lat, lng, radius, page, pageSize)
|
|
if err != nil {
|
|
h.handleError(w, err)
|
|
return
|
|
}
|
|
|
|
h.respondWithJSON(w, http.StatusOK, response)
|
|
}
|
|
|
|
// CreateFeedback обрабатывает POST /objects/{id}/feedbacks
|
|
func (h *ObjectHandler) CreateFeedback(w http.ResponseWriter, r *http.Request) {
|
|
objectID, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
|
if err != nil {
|
|
http.Error(w, "Invalid object ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var req CreateFeedbackRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
req.ObjectID = uint(objectID)
|
|
|
|
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
|
|
if !ok {
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
response, err := h.objectService.CreateFeedback(r.Context(), &req, userID)
|
|
if err != nil {
|
|
h.handleError(w, err)
|
|
return
|
|
}
|
|
|
|
h.respondWithJSON(w, http.StatusCreated, response)
|
|
}
|
|
|
|
// CreateRatingVote обрабатывает POST /objects/{id}/ratings
|
|
func (h *ObjectHandler) CreateRatingVote(w http.ResponseWriter, r *http.Request) {
|
|
objectID, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
|
if err != nil {
|
|
http.Error(w, "Invalid object ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var req CreateRatingVoteRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
req.TargetID = uint(objectID)
|
|
|
|
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
|
|
if !ok {
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
response, err := h.objectService.CreateRatingVote(r.Context(), &req, userID)
|
|
if err != nil {
|
|
h.handleError(w, err)
|
|
return
|
|
}
|
|
|
|
h.respondWithJSON(w, http.StatusCreated, response)
|
|
}
|
|
|
|
// Вспомогательные методы
|
|
|
|
func (h *ObjectHandler) respondWithJSON(w http.ResponseWriter, status int, data interface{}) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
if err := json.NewEncoder(w).Encode(data); err != nil {
|
|
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func (h *ObjectHandler) handleError(w http.ResponseWriter, err error) {
|
|
switch err {
|
|
case ErrObjectNotFound:
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
case ErrInvalidOwnerID, ErrShortNameRequired, ErrAlreadyVoted:
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
case ErrNotImplemented:
|
|
http.Error(w, err.Error(), http.StatusNotImplemented)
|
|
default:
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func (h *ObjectHandler) getQueryParamInt(r *http.Request, key string, defaultValue int) int {
|
|
value := r.URL.Query().Get(key)
|
|
if value == "" {
|
|
return defaultValue
|
|
}
|
|
intValue, err := strconv.Atoi(value)
|
|
if err != nil {
|
|
return defaultValue
|
|
}
|
|
return intValue
|
|
} |