places.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. package handlers
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "strconv"
  7. "github.com/go-chi/chi/v5"
  8. "github.com/photoplaces/backend/internal/middleware"
  9. "github.com/photoplaces/backend/internal/models"
  10. "github.com/photoplaces/backend/internal/services"
  11. )
  12. type PlaceHandler struct {
  13. placeSvc *services.PlaceService
  14. }
  15. func NewPlaceHandler(placeSvc *services.PlaceService) *PlaceHandler {
  16. return &PlaceHandler{placeSvc: placeSvc}
  17. }
  18. func (h *PlaceHandler) List(w http.ResponseWriter, r *http.Request) {
  19. q := r.URL.Query()
  20. filter := models.PlaceFilter{
  21. Type: q.Get("type"),
  22. Status: "published",
  23. }
  24. if tags := q["tags"]; len(tags) > 0 {
  25. filter.TagIDs = tags
  26. }
  27. if features := q["features"]; len(features) > 0 {
  28. filter.FeatureIDs = features
  29. }
  30. if mr := q.Get("min_rating"); mr != "" {
  31. if v, err := strconv.ParseFloat(mr, 64); err == nil {
  32. filter.MinRating = v
  33. }
  34. }
  35. if pmin := q.Get("price_min"); pmin != "" {
  36. if v, err := strconv.Atoi(pmin); err == nil {
  37. filter.PriceMin = &v
  38. }
  39. }
  40. if pmax := q.Get("price_max"); pmax != "" {
  41. if v, err := strconv.Atoi(pmax); err == nil {
  42. filter.PriceMax = &v
  43. }
  44. }
  45. if bounds := q.Get("bounds"); bounds != "" {
  46. var b models.Bounds
  47. if _, err := fmt.Sscanf(bounds, "%f,%f,%f,%f", &b.SWLat, &b.SWLng, &b.NELat, &b.NELng); err == nil {
  48. filter.Bounds = &b
  49. }
  50. }
  51. if limit := q.Get("limit"); limit != "" {
  52. if v, err := strconv.Atoi(limit); err == nil {
  53. filter.Limit_ = v
  54. }
  55. }
  56. filter.Sort = q.Get("sort")
  57. if q.Get("user_lat") != "" && q.Get("user_lng") != "" {
  58. if lat, err := strconv.ParseFloat(q.Get("user_lat"), 64); err == nil {
  59. filter.UserLat = &lat
  60. }
  61. if lng, err := strconv.ParseFloat(q.Get("user_lng"), 64); err == nil {
  62. filter.UserLng = &lng
  63. }
  64. }
  65. places, err := h.placeSvc.List(r.Context(), filter)
  66. if err != nil {
  67. writeError(w, http.StatusInternalServerError, err.Error())
  68. return
  69. }
  70. if places == nil {
  71. places = []*models.Place{}
  72. }
  73. writeJSON(w, http.StatusOK, map[string]interface{}{
  74. "data": places,
  75. })
  76. }
  77. func (h *PlaceHandler) GetByID(w http.ResponseWriter, r *http.Request) {
  78. id := chi.URLParam(r, "id")
  79. place, err := h.placeSvc.GetByID(r.Context(), id, true)
  80. if err != nil {
  81. writeError(w, http.StatusInternalServerError, err.Error())
  82. return
  83. }
  84. if place == nil {
  85. writeError(w, http.StatusNotFound, "place not found")
  86. return
  87. }
  88. writeJSON(w, http.StatusOK, place)
  89. }
  90. type createPlaceRequest struct {
  91. Title string `json:"title"`
  92. Description *string `json:"description"`
  93. Address *string `json:"address"`
  94. Lat float64 `json:"lat"`
  95. Lng float64 `json:"lng"`
  96. Type string `json:"type"`
  97. AccessInfo *string `json:"access_info"`
  98. CoverImage *string `json:"cover_image"`
  99. Tags []string `json:"tags"`
  100. Features []string `json:"features"`
  101. HourlyRate *int `json:"hourly_rate"`
  102. Currency string `json:"currency"`
  103. MinHours int `json:"min_hours"`
  104. }
  105. func (h *PlaceHandler) Create(w http.ResponseWriter, r *http.Request) {
  106. userID := middleware.GetUserID(r.Context())
  107. role := middleware.GetUserRole(r.Context())
  108. var req createPlaceRequest
  109. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  110. writeError(w, http.StatusBadRequest, "invalid request body")
  111. return
  112. }
  113. if req.Title == "" {
  114. writeError(w, http.StatusBadRequest, "title is required")
  115. return
  116. }
  117. if req.Type == "" {
  118. writeError(w, http.StatusBadRequest, "type is required (place or studio)")
  119. return
  120. }
  121. if req.Type == "studio" && role != "landlord" && role != "moderator" && role != "superadmin" {
  122. writeError(w, http.StatusForbidden, "only landlords can create studios")
  123. return
  124. }
  125. if req.Type == "place" && role != "customer" && role != "moderator" && role != "superadmin" {
  126. writeError(w, http.StatusForbidden, "only customers can create places")
  127. return
  128. }
  129. tags := make([]models.Tag, len(req.Tags))
  130. for i, t := range req.Tags {
  131. tags[i] = models.Tag{ID: t}
  132. }
  133. features := make([]models.Feature, len(req.Features))
  134. for i, f := range req.Features {
  135. features[i] = models.Feature{ID: f}
  136. }
  137. place, err := h.placeSvc.Create(r.Context(), services.CreatePlaceInput{
  138. OwnerID: userID,
  139. Type: req.Type,
  140. Title: req.Title,
  141. Description: req.Description,
  142. Address: req.Address,
  143. Lat: req.Lat,
  144. Lng: req.Lng,
  145. CoverImage: req.CoverImage,
  146. AccessInfo: req.AccessInfo,
  147. Tags: tags,
  148. Features: features,
  149. HourlyRate: req.HourlyRate,
  150. Currency: req.Currency,
  151. MinHours: req.MinHours,
  152. })
  153. if err != nil {
  154. writeError(w, http.StatusInternalServerError, err.Error())
  155. return
  156. }
  157. writeJSON(w, http.StatusCreated, place)
  158. }
  159. func (h *PlaceHandler) Update(w http.ResponseWriter, r *http.Request) {
  160. id := chi.URLParam(r, "id")
  161. userID := middleware.GetUserID(r.Context())
  162. role := middleware.GetUserRole(r.Context())
  163. var req createPlaceRequest
  164. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  165. writeError(w, http.StatusBadRequest, "invalid request body")
  166. return
  167. }
  168. isModerator := role == "moderator" || role == "superadmin"
  169. input := services.UpdatePlaceInput{
  170. ID: id,
  171. OwnerID: userID,
  172. }
  173. if req.Title != "" { input.Title = &req.Title }
  174. input.Description = req.Description
  175. input.Address = req.Address
  176. if req.Lat != 0 { input.Lat = &req.Lat }
  177. if req.Lng != 0 { input.Lng = &req.Lng }
  178. input.AccessInfo = req.AccessInfo
  179. input.HourlyRate = req.HourlyRate
  180. if req.Currency != "" { input.Currency = &req.Currency }
  181. if req.MinHours != 0 { input.MinHours = &req.MinHours }
  182. place, err := h.placeSvc.Update(r.Context(), input, isModerator)
  183. if err != nil {
  184. writeError(w, http.StatusInternalServerError, err.Error())
  185. return
  186. }
  187. if place == nil {
  188. writeError(w, http.StatusNotFound, "place not found")
  189. return
  190. }
  191. writeJSON(w, http.StatusOK, place)
  192. }
  193. func (h *PlaceHandler) Moderate(w http.ResponseWriter, r *http.Request) {
  194. id := chi.URLParam(r, "id")
  195. userID := middleware.GetUserID(r.Context())
  196. var req struct {
  197. Action string `json:"action"`
  198. Comment string `json:"comment"`
  199. }
  200. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  201. writeError(w, http.StatusBadRequest, "invalid request body")
  202. return
  203. }
  204. if err := h.placeSvc.Moderate(r.Context(), id, req.Action, req.Comment, userID); err != nil {
  205. writeError(w, http.StatusInternalServerError, err.Error())
  206. return
  207. }
  208. writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
  209. }
  210. func (h *PlaceHandler) Delete(w http.ResponseWriter, r *http.Request) {
  211. id := chi.URLParam(r, "id")
  212. userID := middleware.GetUserID(r.Context())
  213. role := middleware.GetUserRole(r.Context())
  214. place, err := h.placeSvc.GetByID(r.Context(), id, false)
  215. if err != nil {
  216. writeError(w, http.StatusInternalServerError, err.Error())
  217. return
  218. }
  219. if place == nil {
  220. writeError(w, http.StatusNotFound, "place not found")
  221. return
  222. }
  223. if place.OwnerID != userID && role != "moderator" && role != "superadmin" {
  224. writeError(w, http.StatusForbidden, "not your place")
  225. return
  226. }
  227. if err := h.placeSvc.Delete(r.Context(), id); err != nil {
  228. writeError(w, http.StatusInternalServerError, err.Error())
  229. return
  230. }
  231. w.WriteHeader(http.StatusNoContent)
  232. }