package handlers import ( "encoding/json" "fmt" "net/http" "strconv" "github.com/go-chi/chi/v5" "github.com/photoplaces/backend/internal/middleware" "github.com/photoplaces/backend/internal/models" "github.com/photoplaces/backend/internal/services" ) type PlaceHandler struct { placeSvc *services.PlaceService } func NewPlaceHandler(placeSvc *services.PlaceService) *PlaceHandler { return &PlaceHandler{placeSvc: placeSvc} } func (h *PlaceHandler) List(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() filter := models.PlaceFilter{ Type: q.Get("type"), Status: "published", } if tags := q["tags"]; len(tags) > 0 { filter.TagIDs = tags } if features := q["features"]; len(features) > 0 { filter.FeatureIDs = features } if mr := q.Get("min_rating"); mr != "" { if v, err := strconv.ParseFloat(mr, 64); err == nil { filter.MinRating = v } } if pmin := q.Get("price_min"); pmin != "" { if v, err := strconv.Atoi(pmin); err == nil { filter.PriceMin = &v } } if pmax := q.Get("price_max"); pmax != "" { if v, err := strconv.Atoi(pmax); err == nil { filter.PriceMax = &v } } if bounds := q.Get("bounds"); bounds != "" { var b models.Bounds if _, err := fmt.Sscanf(bounds, "%f,%f,%f,%f", &b.SWLat, &b.SWLng, &b.NELat, &b.NELng); err == nil { filter.Bounds = &b } } if limit := q.Get("limit"); limit != "" { if v, err := strconv.Atoi(limit); err == nil { filter.Limit_ = v } } filter.Sort = q.Get("sort") if q.Get("user_lat") != "" && q.Get("user_lng") != "" { if lat, err := strconv.ParseFloat(q.Get("user_lat"), 64); err == nil { filter.UserLat = &lat } if lng, err := strconv.ParseFloat(q.Get("user_lng"), 64); err == nil { filter.UserLng = &lng } } places, err := h.placeSvc.List(r.Context(), filter) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } if places == nil { places = []*models.Place{} } writeJSON(w, http.StatusOK, map[string]interface{}{ "data": places, }) } func (h *PlaceHandler) GetByID(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") place, err := h.placeSvc.GetByID(r.Context(), id, true) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } if place == nil { writeError(w, http.StatusNotFound, "place not found") return } writeJSON(w, http.StatusOK, place) } type createPlaceRequest struct { Title string `json:"title"` Description *string `json:"description"` Address *string `json:"address"` Lat float64 `json:"lat"` Lng float64 `json:"lng"` Type string `json:"type"` AccessInfo *string `json:"access_info"` CoverImage *string `json:"cover_image"` Tags []string `json:"tags"` Features []string `json:"features"` HourlyRate *int `json:"hourly_rate"` Currency string `json:"currency"` MinHours int `json:"min_hours"` } func (h *PlaceHandler) Create(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) role := middleware.GetUserRole(r.Context()) var req createPlaceRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if req.Title == "" { writeError(w, http.StatusBadRequest, "title is required") return } if req.Type == "" { writeError(w, http.StatusBadRequest, "type is required (place or studio)") return } if req.Type == "studio" && role != "landlord" && role != "moderator" && role != "superadmin" { writeError(w, http.StatusForbidden, "only landlords can create studios") return } if req.Type == "place" && role != "customer" && role != "moderator" && role != "superadmin" { writeError(w, http.StatusForbidden, "only customers can create places") return } tags := make([]models.Tag, len(req.Tags)) for i, t := range req.Tags { tags[i] = models.Tag{ID: t} } features := make([]models.Feature, len(req.Features)) for i, f := range req.Features { features[i] = models.Feature{ID: f} } place, err := h.placeSvc.Create(r.Context(), services.CreatePlaceInput{ OwnerID: userID, Type: req.Type, Title: req.Title, Description: req.Description, Address: req.Address, Lat: req.Lat, Lng: req.Lng, CoverImage: req.CoverImage, AccessInfo: req.AccessInfo, Tags: tags, Features: features, HourlyRate: req.HourlyRate, Currency: req.Currency, MinHours: req.MinHours, }) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusCreated, place) } func (h *PlaceHandler) Update(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") userID := middleware.GetUserID(r.Context()) role := middleware.GetUserRole(r.Context()) var req createPlaceRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } isModerator := role == "moderator" || role == "superadmin" input := services.UpdatePlaceInput{ ID: id, OwnerID: userID, } if req.Title != "" { input.Title = &req.Title } input.Description = req.Description input.Address = req.Address if req.Lat != 0 { input.Lat = &req.Lat } if req.Lng != 0 { input.Lng = &req.Lng } input.AccessInfo = req.AccessInfo input.HourlyRate = req.HourlyRate if req.Currency != "" { input.Currency = &req.Currency } if req.MinHours != 0 { input.MinHours = &req.MinHours } place, err := h.placeSvc.Update(r.Context(), input, isModerator) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } if place == nil { writeError(w, http.StatusNotFound, "place not found") return } writeJSON(w, http.StatusOK, place) } func (h *PlaceHandler) Moderate(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") userID := middleware.GetUserID(r.Context()) var req struct { Action string `json:"action"` Comment string `json:"comment"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if err := h.placeSvc.Moderate(r.Context(), id, req.Action, req.Comment, userID); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) } func (h *PlaceHandler) Delete(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") userID := middleware.GetUserID(r.Context()) role := middleware.GetUserRole(r.Context()) place, err := h.placeSvc.GetByID(r.Context(), id, false) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } if place == nil { writeError(w, http.StatusNotFound, "place not found") return } if place.OwnerID != userID && role != "moderator" && role != "superadmin" { writeError(w, http.StatusForbidden, "not your place") return } if err := h.placeSvc.Delete(r.Context(), id); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } w.WriteHeader(http.StatusNoContent) }