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  }