eintopf.info@v0.13.16/service/revent/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 revent 17 18 import ( 19 "encoding/json" 20 "fmt" 21 "io" 22 "log" 23 "net/http" 24 "strconv" 25 "time" 26 27 "github.com/go-chi/chi/v5" 28 29 "eintopf.info/internal/crud" 30 "eintopf.info/internal/xerror" 31 "eintopf.info/internal/xhttp" 32 "eintopf.info/service/auth" 33 "eintopf.info/service/event" 34 ) 35 36 // Router returns a new http router that handles crud RepeatingEvent requests for a given 37 // RepeatingEvent service. 38 func Router(service Service, authService auth.Service) func(chi.Router) { 39 router := &router{service} 40 return func(r chi.Router) { 41 r.Options("/", xhttp.CorsHandler) 42 43 // swagger:route GET /revents/ RepeatingEvents findRepeatingEvents 44 // 45 // Retrieves all RepeatingEvents. 46 // 47 // Parameters: 48 // + name: offset 49 // description: offset in event list 50 // in: query 51 // type: integer 52 // required: false 53 // + name: limit 54 // description: limits the event list 55 // in: query 56 // type: integer 57 // required: false 58 // + name: sort 59 // description: field that gets sorted 60 // in: query 61 // type: string 62 // required: false 63 // + name: order 64 // description: sort order ("ASC" or "DESC") 65 // in: query 66 // type: string 67 // required: false 68 // + name: filters 69 // description: filters get combined with AND logic 70 // in: query 71 // type: object 72 // required: false 73 // 74 // Responses: 75 // 200: findRepeatingEventsResponse 76 // 400: badRequest 77 // 401: unauthorizedError 78 // 500: internalError 79 // 500: internalError 80 // 503: serviceUnavailable 81 r.With(auth.MiddlewareWithOpts(authService, auth.MiddlewareOpts{Validate: false})). 82 Get("/", router.find) 83 84 // swagger:route POST /revents/ RepeatingEvent createRepeatingEvent 85 // 86 // Creates a new RepeatingEvent with the given data. 87 // Security: 88 // bearer: [] 89 // 90 // Responses: 91 // 200: createRepeatingEventResponse 92 // 400: badRequest 93 // 401: unauthorizedError 94 // 500: internalError 95 // 503: serviceUnavailable 96 r.With(auth.Middleware(authService)). 97 Post("/", router.create) 98 99 r.Options("/{id}", xhttp.CorsHandler) 100 101 // swagger:route GET /revents/{id} RepeatingEvent findRepeatingEvent 102 // 103 // Finds the RepeatingEvent with the given id. 104 // 105 // Responses: 106 // 200: findRepeatingEventResponse 107 // 400: badRequest 108 // 401: unauthorizedError 109 // 500: internalError 110 // 503: serviceUnavailable 111 r.Get("/{id}", router.findByID) 112 113 // swagger:route PUT /revents/{id} RepeatingEvent updateRepeatingEvent 114 // 115 // Updates the RepeatingEvent with the given id. 116 // Security: 117 // bearer: [] 118 // 119 // Responses: 120 // 200: updateRepeatingEventResponse 121 // 400: badRequest 122 // 401: unauthorizedError 123 // 500: internalError 124 // 503: serviceUnavailable 125 r.With(auth.Middleware(authService)). 126 Put("/{id}", router.update) 127 128 // swagger:route DELETE /revents/{id} RepeatingEvent deleteRepeatingEvent 129 // 130 // Deltes the RepeatingEvent with the given id. 131 // Security: 132 // bearer: [] 133 // 134 // Responses: 135 // 200: deleteRepeatingEventResponse 136 // 400: badRequest 137 // 401: unauthorizedError 138 // 500: internalError 139 // 503: serviceUnavailable 140 r.With(auth.Middleware(authService)). 141 Delete("/{id}", router.delete) 142 143 r.Options("/{id}/generate", xhttp.CorsHandler) 144 145 // swagger:route GET /revents/{id}/generate RepeatingEvent generateRepeatingEvent 146 // 147 // Generates Events from the repeating event 148 // Security: 149 // bearer: [] 150 // 151 // Responses: 152 // 200: generateRepeatingEventResponse 153 // 400: badRequest 154 // 401: unauthorizedError 155 // 500: internalError 156 // 503: serviceUnavailable 157 r.With(auth.MiddlewareWithOpts(authService, auth.MiddlewareOpts{Validate: true})). 158 Get("/{id}/generate", router.generate) 159 } 160 } 161 162 type router struct { 163 s Service 164 } 165 166 func (router *router) find(w http.ResponseWriter, r *http.Request) { 167 params, err := readFindRequest(r) 168 if err != nil { 169 xhttp.WriteBadRequest(r.Context(), w, fmt.Errorf("failed to read findAll request: %s", err)) 170 return 171 } 172 RepeatingEvents, totalRepeatingEvents, err := router.s.Find(r.Context(), params) 173 if err != nil { 174 xhttp.WriteError(r.Context(), w, err) 175 return 176 } 177 err = writeFindResponse(w, &findResponse{RepeatingEvents: RepeatingEvents, TotalRepeatingEvents: totalRepeatingEvents}) 178 if err != nil { 179 log.Println(fmt.Errorf("failed to write findAll response: %s", err)) 180 return 181 } 182 } 183 184 // swagger:response findRepeatingEventsResponse 185 type findResponse struct { 186 187 // in:body 188 RepeatingEvents []*RepeatingEvent 189 190 // in:header 191 TotalRepeatingEvents int `json:"x-total-count"` 192 } 193 194 func readFindRequest(r *http.Request) (*crud.FindParams[FindFilters], error) { 195 params := &crud.FindParams[FindFilters]{} 196 var err error 197 198 params.Offset, err = xhttp.ReadQueryInt64(r, "offset") 199 if err != nil { 200 return nil, err 201 } 202 params.Limit, err = xhttp.ReadQueryInt64(r, "limit") 203 if err != nil { 204 return nil, err 205 } 206 207 params.Sort, err = xhttp.ReadQueryString(r, "sorting") 208 if err != nil { 209 return nil, err 210 } 211 order, err := xhttp.ReadQueryString(r, "order") 212 if err != nil { 213 return nil, err 214 } 215 params.Order = crud.SortOrder(order) 216 217 filters := &FindFilters{} 218 err = xhttp.ReadQueryJson(r, "filters", filters) 219 if err != nil { 220 return nil, err 221 } 222 params.Filters = filters 223 224 return params, nil 225 } 226 227 func writeFindResponse(w http.ResponseWriter, resp *findResponse) error { 228 data, err := json.Marshal(resp.RepeatingEvents) 229 if err != nil { 230 return err 231 } 232 w.Header().Add("Access-Control-Expose-Headers", "X-Total-Count") 233 w.Header().Add("X-Total-Count", strconv.Itoa(resp.TotalRepeatingEvents)) 234 w.Write(data) 235 return nil 236 } 237 238 func (router *router) create(w http.ResponseWriter, r *http.Request) { 239 req, err := readCreateRequest(r) 240 if err != nil { 241 xhttp.WriteBadRequest(r.Context(), w, fmt.Errorf("failed to read create request: %s", err)) 242 return 243 } 244 RepeatingEvent, err := router.s.Create(r.Context(), req.RepeatingEvent) 245 if err != nil { 246 xhttp.WriteError(r.Context(), w, err) 247 return 248 } 249 err = writeCreateResponse(w, &createResponse{RepeatingEvent}) 250 if err != nil { 251 log.Println(fmt.Errorf("failed to write create response: %s", err)) 252 return 253 } 254 } 255 256 // swagger:parameters createRepeatingEvent 257 type createRequest struct { 258 259 // in:body 260 RepeatingEvent *NewRepeatingEvent 261 } 262 263 // swagger:response createRepeatingEventsResponse 264 type createResponse struct { 265 266 // in:body 267 RepeatingEvent *RepeatingEvent 268 } 269 270 func readCreateRequest(r *http.Request) (*createRequest, error) { 271 data, err := io.ReadAll(r.Body) 272 if err != nil { 273 return nil, err 274 } 275 RepeatingEvent, err := decodeNewRepeatingEvent(data) 276 if err != nil { 277 return nil, err 278 } 279 return &createRequest{RepeatingEvent}, nil 280 } 281 282 func writeCreateResponse(w http.ResponseWriter, resp *createResponse) error { 283 data, err := encodeRepeatingEvent(resp.RepeatingEvent) 284 if err != nil { 285 return err 286 } 287 w.Write(data) 288 return nil 289 } 290 291 func (router *router) findByID(w http.ResponseWriter, r *http.Request) { 292 req, err := readFindByIDRequest(r) 293 if err != nil { 294 xhttp.WriteBadRequest(r.Context(), w, fmt.Errorf("failed to read find request: %s", err)) 295 return 296 } 297 repeatingEvent, err := router.s.FindByID(r.Context(), req.ID) 298 if err != nil { 299 xhttp.WriteError(r.Context(), w, err) 300 return 301 } 302 err = writeFindByIDResponse(w, &findByIDResponse{repeatingEvent}) 303 if err != nil { 304 log.Println(fmt.Errorf("failed to write find response: %s", err)) 305 return 306 } 307 } 308 309 // swagger:parameters findRepeatingEvent 310 type findByIDRequest struct { 311 // in:query 312 ID string `json:"id"` 313 } 314 315 // swagger:response findRepeatingEventResponse 316 type findByIDResponse struct { 317 318 // in:body 319 RepeatingEvent *RepeatingEvent 320 } 321 322 func readFindByIDRequest(r *http.Request) (*findByIDRequest, error) { 323 id := chi.URLParam(r, "id") 324 return &findByIDRequest{id}, nil 325 } 326 327 func writeFindByIDResponse(w http.ResponseWriter, resp *findByIDResponse) error { 328 data, err := encodeRepeatingEvent(resp.RepeatingEvent) 329 if err != nil { 330 return err 331 } 332 w.Write(data) 333 return nil 334 } 335 336 func (router *router) update(w http.ResponseWriter, r *http.Request) { 337 req, err := readUpdateRequest(r) 338 if err != nil { 339 xhttp.WriteBadRequest(r.Context(), w, fmt.Errorf("failed to read update request: %s", err)) 340 return 341 } 342 RepeatingEvent, err := router.s.Update(r.Context(), req.RepeatingEvent) 343 if err != nil { 344 xhttp.WriteError(r.Context(), w, err) 345 return 346 } 347 err = writeUpdateResponse(w, &updateResponse{RepeatingEvent}) 348 if err != nil { 349 log.Println(fmt.Errorf("failed to write update response: %s", err)) 350 return 351 } 352 } 353 354 // swagger:parameters updateRepeatingEvent 355 type updateRequest struct { 356 357 // in:body 358 RepeatingEvent *RepeatingEvent 359 } 360 361 // swagger:response updateRepeatingEventResponse 362 type updateResponse struct { 363 364 // in:body 365 RepeatingEvent *RepeatingEvent 366 } 367 368 func readUpdateRequest(r *http.Request) (*updateRequest, error) { 369 data, err := io.ReadAll(r.Body) 370 if err != nil { 371 return nil, err 372 } 373 RepeatingEvent, err := decodeRepeatingEvent(data) 374 if err != nil { 375 return nil, err 376 } 377 return &updateRequest{RepeatingEvent}, nil 378 } 379 380 func writeUpdateResponse(w http.ResponseWriter, resp *updateResponse) error { 381 data, err := encodeRepeatingEvent(resp.RepeatingEvent) 382 if err != nil { 383 return err 384 } 385 w.Write(data) 386 return nil 387 } 388 389 func (router *router) delete(w http.ResponseWriter, r *http.Request) { 390 req, err := readDeleteRequest(r) 391 if err != nil { 392 393 xhttp.WriteBadRequest(r.Context(), w, fmt.Errorf("failed to read delete request: %s", err)) 394 return 395 } 396 err = router.s.Delete(r.Context(), req.ID) 397 if err != nil { 398 xhttp.WriteError(r.Context(), w, err) 399 return 400 } 401 err = writeDeleteResponse(w) 402 if err != nil { 403 log.Println(fmt.Errorf("failed to write delete response: %s", err)) 404 return 405 } 406 } 407 408 // swagger:parameters deleteRepeatingEvent 409 type deleteRequest struct { 410 411 // in:query 412 ID string `json:"id"` 413 } 414 415 // swagger:response deleteRepeatingEventResponse 416 type deleteResponse struct{} 417 418 func readDeleteRequest(r *http.Request) (*deleteRequest, error) { 419 id := chi.URLParam(r, "id") 420 return &deleteRequest{id}, nil 421 } 422 423 func writeDeleteResponse(w http.ResponseWriter) error { 424 w.Write([]byte{}) 425 return nil 426 } 427 428 func (router *router) generate(w http.ResponseWriter, r *http.Request) { 429 req, err := readGenerateRequest(r) 430 if err != nil { 431 xhttp.WriteBadRequest(r.Context(), w, fmt.Errorf("failed to generate events: %s", err)) 432 return 433 } 434 events, err := router.s.GenerateEvents(r.Context(), req.ID, req.Start, req.End) 435 if xerror.IsBadInputError(err) { 436 xhttp.WriteBadRequest(r.Context(), w, err) 437 return 438 } 439 if err != nil { 440 xhttp.WriteInternalError(r.Context(), w, fmt.Errorf("failed to generate events: %s", err)) 441 return 442 } 443 err = writeGenerateResponse(w, &generateResponse{events}) 444 if err != nil { 445 log.Println(fmt.Errorf("failed to write find response: %s", err)) 446 return 447 } 448 } 449 450 // swagger:parameters generateRepeatingEvent 451 type generateRequest struct { 452 // in:query 453 ID string `json:"id"` 454 455 // in:query 456 Start time.Time `json:"start"` 457 458 // in:query 459 End time.Time `json:"end"` 460 } 461 462 // swagger:response generateRepeatingEventResponse 463 type generateResponse struct { 464 465 // in:body 466 Events []*event.Event 467 } 468 469 func readGenerateRequest(r *http.Request) (*generateRequest, error) { 470 id := chi.URLParam(r, "id") 471 startRaw := r.URL.Query().Get("start") 472 start, err := time.Parse(time.RFC3339, startRaw) 473 if err != nil { 474 return nil, fmt.Errorf("parse start time: %s", err) 475 } 476 endRaw := r.URL.Query().Get("end") 477 end, err := time.Parse(time.RFC3339, endRaw) 478 if err != nil { 479 return nil, fmt.Errorf("parse end time: %s", err) 480 } 481 return &generateRequest{ID: id, Start: start, End: end}, nil 482 } 483 484 func writeGenerateResponse(w http.ResponseWriter, resp *generateResponse) error { 485 data, err := json.Marshal(resp.Events) 486 if err != nil { 487 return err 488 } 489 w.Write(data) 490 return nil 491 } 492 493 func decodeRepeatingEvent(data []byte) (*RepeatingEvent, error) { 494 RepeatingEvent := &RepeatingEvent{} 495 err := json.Unmarshal(data, RepeatingEvent) 496 return RepeatingEvent, err 497 } 498 499 func encodeRepeatingEvent(RepeatingEvent *RepeatingEvent) ([]byte, error) { 500 return json.Marshal(RepeatingEvent) 501 } 502 503 func encodeRepeatingEvents(RepeatingEvents []RepeatingEvent) ([]byte, error) { 504 return json.Marshal(RepeatingEvents) 505 } 506 507 func decodeNewRepeatingEvent(data []byte) (*NewRepeatingEvent, error) { 508 RepeatingEvent := &NewRepeatingEvent{} 509 err := json.Unmarshal(data, RepeatingEvent) 510 return RepeatingEvent, err 511 }