eintopf.info@v0.13.16/service/place/transport.go (about) 1 // Copyright (C) 2022 The Eintopf authors 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <https://www.gnu.org/licenses/>. 15 16 package place 17 18 import ( 19 "encoding/json" 20 "io" 21 "log" 22 "net/http" 23 "strconv" 24 25 "github.com/go-chi/chi/v5" 26 27 "eintopf.info/internal/crud" 28 "eintopf.info/internal/xhttp" 29 "eintopf.info/service/auth" 30 ) 31 32 // Router returns a new http router that handles crud requests on the place 33 // model, that get forwarded to the given place service. 34 func Router(service Service, authService auth.Service) func(chi.Router) { 35 server := &server{service} 36 return func(r chi.Router) { 37 r.Options("/", xhttp.CorsHandler) 38 39 // swagger:route GET /places/ place findPlace 40 // 41 // Retrieves all places. 42 // 43 // Parameters: 44 // + name: offset 45 // description: offset in event list 46 // in: query 47 // type: integer 48 // required: false 49 // + name: limit 50 // description: limits the event list 51 // in: query 52 // type: integer 53 // required: false 54 // + name: sort 55 // description: field that gets sorted 56 // in: query 57 // type: string 58 // required: false 59 // + name: order 60 // description: sort order ("ASC" or "DESC") 61 // in: query 62 // type: string 63 // required: false 64 // + name: filters 65 // description: filters get combined with AND logic 66 // in: query 67 // type: object 68 // required: false 69 // 70 // Responses: 71 // 200: findPlacesResponse 72 // 400: badRequest 73 // 401: unauthorizedError 74 // 500: internalError 75 // 503: serviceUnavailable 76 r.With(auth.MiddlewareWithOpts(authService, auth.MiddlewareOpts{Validate: false})). 77 Get("/", server.find) 78 79 // swagger:route POST /places/ place createPlace 80 // 81 // Creates a new place with the given data. 82 // Security: 83 // bearer: [] 84 // 85 // Responses: 86 // 200: createPlaceResponse 87 // 400: badRequest 88 // 401: unauthorizedError 89 // 500: internalError 90 // 503: serviceUnavailable 91 r.With(auth.Middleware(authService)). 92 Post("/", server.create) 93 94 r.Options("/{id}", xhttp.CorsHandler) 95 96 // swagger:route GET /places/{id} place findPlace 97 // 98 // Finds the place with the given id. 99 // 100 // Responses: 101 // 200: findPlaceResponse 102 // 400: badRequest 103 // 401: unauthorizedError 104 // 500: internalError 105 // 503: serviceUnavailable 106 r.Get("/{id}", server.findByID) 107 108 // swagger:route PUT /places/{id} place updatePlace 109 // 110 // Updates the place with the given id. 111 // Security: 112 // bearer: [] 113 // 114 // Responses: 115 // 200: updatePlaceResponse 116 // 400: badRequest 117 // 401: unauthorizedError 118 // 500: internalError 119 // 503: serviceUnavailable 120 r.With(auth.Middleware(authService)). 121 Put("/{id}", server.update) 122 123 // swagger:route DELETE /places/{id} place deletePlace 124 // 125 // Deletes the place with the given id. 126 // Security: 127 // bearer: [] 128 // 129 // Responses: 130 // 200: deletePlaceResponse 131 // 400: badRequest 132 // 401: unauthorizedError 133 // 500: internalError 134 // 503: serviceUnavailable 135 r.With(auth.Middleware(authService)). 136 Delete("/{id}", server.delete) 137 } 138 } 139 140 type server struct { 141 service Service 142 } 143 144 func (s *server) find(w http.ResponseWriter, r *http.Request) { 145 params, err := readFindRequest(r) 146 if err != nil { 147 xhttp.WriteBadRequest(r.Context(), w, err) 148 return 149 } 150 places, total, err := s.service.Find(r.Context(), params) 151 if err != nil { 152 xhttp.WriteError(r.Context(), w, err) 153 return 154 } 155 err = writeFindResponse(w, &findResponse{Places: places, Total: total}) 156 if err != nil { 157 log.Println(err) 158 return 159 } 160 } 161 162 // swagger:response findPlacesResponse 163 type findResponse struct { 164 165 // in:body 166 Places []*Place 167 168 // in:header 169 Total int `json:"x-total-count"` 170 } 171 172 func readFindRequest(r *http.Request) (*crud.FindParams[FindFilters], error) { 173 params := &crud.FindParams[FindFilters]{} 174 var err error 175 176 params.Offset, err = xhttp.ReadQueryInt64(r, "offset") 177 if err != nil { 178 return nil, err 179 } 180 params.Limit, err = xhttp.ReadQueryInt64(r, "limit") 181 if err != nil { 182 return nil, err 183 } 184 185 params.Sort, err = xhttp.ReadQueryString(r, "sorting") 186 if err != nil { 187 return nil, err 188 } 189 order, err := xhttp.ReadQueryString(r, "order") 190 if err != nil { 191 return nil, err 192 } 193 params.Order = crud.SortOrder(order) 194 195 filters := &FindFilters{} 196 err = xhttp.ReadQueryJson(r, "filters", filters) 197 if err != nil { 198 return nil, err 199 } 200 params.Filters = filters 201 202 return params, nil 203 } 204 205 func writeFindResponse(w http.ResponseWriter, resp *findResponse) error { 206 data, err := json.Marshal(resp.Places) 207 if err != nil { 208 return err 209 } 210 w.Header().Add("Access-Control-Expose-Headers", "X-Total-Count") 211 w.Header().Add("X-Total-Count", strconv.Itoa(resp.Total)) 212 w.Write(data) 213 return nil 214 } 215 216 func (s *server) create(w http.ResponseWriter, r *http.Request) { 217 req, err := readCreateRequest(r) 218 if err != nil { 219 xhttp.WriteBadRequest(r.Context(), w, err) 220 return 221 } 222 place, err := s.service.Create(r.Context(), req.Place) 223 if err != nil { 224 xhttp.WriteError(r.Context(), w, err) 225 return 226 } 227 err = writeCreateResponse(w, &createResponse{place}) 228 if err != nil { 229 log.Println(err) 230 return 231 } 232 } 233 234 // swagger:parameters createPlace 235 type createRequest struct { 236 237 // in:body 238 Place *NewPlace 239 } 240 241 // swagger:response createPlaceResponse 242 type createResponse struct { 243 244 // in:body 245 Place *Place 246 } 247 248 func readCreateRequest(r *http.Request) (*createRequest, error) { 249 data, err := io.ReadAll(r.Body) 250 if err != nil { 251 return nil, err 252 } 253 place := &NewPlace{} 254 err = json.Unmarshal(data, place) 255 if err != nil { 256 return nil, err 257 } 258 return &createRequest{place}, nil 259 } 260 261 func writeCreateResponse(w http.ResponseWriter, resp *createResponse) error { 262 data, err := json.Marshal(resp.Place) 263 if err != nil { 264 return err 265 } 266 w.Write(data) 267 return nil 268 } 269 270 func (s *server) findByID(w http.ResponseWriter, r *http.Request) { 271 req, err := readFindByIDRequest(r) 272 if err != nil { 273 xhttp.WriteBadRequest(r.Context(), w, err) 274 return 275 } 276 place, err := s.service.FindByID(r.Context(), req.ID) 277 if err != nil { 278 xhttp.WriteError(r.Context(), w, err) 279 return 280 } 281 err = writeFindByIDResponse(w, &findByIDResponse{place}) 282 if err != nil { 283 log.Println(err) 284 return 285 } 286 } 287 288 // swagger:parameters findPlace 289 type findByIDRequest struct { 290 // in:query 291 ID string `json:"id"` 292 } 293 294 // swagger:response findPlaceResponse 295 type findByIDResponse struct { 296 297 // in:body 298 Place *Place 299 } 300 301 func readFindByIDRequest(r *http.Request) (*findByIDRequest, error) { 302 id := chi.URLParam(r, "id") 303 return &findByIDRequest{id}, nil 304 } 305 306 func writeFindByIDResponse(w http.ResponseWriter, resp *findByIDResponse) error { 307 data, err := json.Marshal(resp.Place) 308 if err != nil { 309 return err 310 } 311 w.Write(data) 312 return nil 313 } 314 315 func (s *server) update(w http.ResponseWriter, r *http.Request) { 316 req, err := readUpdateRequest(r) 317 if err != nil { 318 xhttp.WriteBadRequest(r.Context(), w, err) 319 return 320 } 321 place, err := s.service.Update(r.Context(), req.Place) 322 if err != nil { 323 xhttp.WriteError(r.Context(), w, err) 324 return 325 } 326 err = writeUpdateResponse(w, &updateResponse{place}) 327 if err != nil { 328 log.Println(err) 329 return 330 } 331 } 332 333 // swagger:parameters updatePlace 334 type updateRequest struct { 335 336 // in:body 337 Place *Place 338 } 339 340 // swagger:response updatePlaceResponse 341 type updateResponse struct { 342 343 // in:body 344 Place *Place 345 } 346 347 func readUpdateRequest(r *http.Request) (*updateRequest, error) { 348 data, err := io.ReadAll(r.Body) 349 if err != nil { 350 return nil, err 351 } 352 place := &Place{} 353 err = json.Unmarshal(data, place) 354 if err != nil { 355 return nil, err 356 } 357 return &updateRequest{place}, nil 358 } 359 360 func writeUpdateResponse(w http.ResponseWriter, resp *updateResponse) error { 361 data, err := json.Marshal(resp.Place) 362 if err != nil { 363 return err 364 } 365 w.Write(data) 366 return nil 367 } 368 369 func (s *server) delete(w http.ResponseWriter, r *http.Request) { 370 req, err := readDeleteRequest(r) 371 if err != nil { 372 373 xhttp.WriteBadRequest(r.Context(), w, err) 374 return 375 } 376 err = s.service.Delete(r.Context(), req.ID) 377 if err != nil { 378 xhttp.WriteError(r.Context(), w, err) 379 return 380 } 381 err = writeDeleteResponse(w) 382 if err != nil { 383 log.Println(err) 384 return 385 } 386 } 387 388 // swagger:parameters deletePlace 389 type deleteRequest struct { 390 391 // in:query 392 ID string `json:"id"` 393 } 394 395 // swagger:response deletePlaceResponse 396 type deleteResponse struct{} 397 398 func readDeleteRequest(r *http.Request) (*deleteRequest, error) { 399 id := chi.URLParam(r, "id") 400 return &deleteRequest{id}, nil 401 } 402 403 func writeDeleteResponse(w http.ResponseWriter) error { 404 w.Write([]byte{}) 405 return nil 406 }