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