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  }