eintopf.info@v0.13.16/service/search/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 search
    17  
    18  import (
    19  	"encoding/json"
    20  	"fmt"
    21  	"net/http"
    22  
    23  	"github.com/go-chi/chi/v5"
    24  
    25  	"eintopf.info/internal/xhttp"
    26  )
    27  
    28  // Router returns an http router for the search service.
    29  func Router(service Service) func(chi.Router) {
    30  	server := &server{service}
    31  	return func(r chi.Router) {
    32  		r.Options("/", xhttp.CorsHandler)
    33  
    34  		// swagger:route GET /search search search
    35  		//
    36  		// Perfomes a search.
    37  		//
    38  		//     Responses:
    39  		//       200: searchResponse
    40  		//       400: badRequest
    41  		//       500: internalError
    42  		//       503: serviceUnavailable
    43  		r.With(xhttp.LastModified(service)).Get("/", server.search)
    44  	}
    45  }
    46  
    47  type server struct {
    48  	service Service
    49  }
    50  
    51  func (s *server) search(w http.ResponseWriter, r *http.Request) {
    52  	req, err := readSearchRequest(r)
    53  	if err != nil {
    54  		xhttp.WriteBadRequest(r.Context(), w, fmt.Errorf("failed to read search request: %s", err))
    55  		return
    56  	}
    57  
    58  	result, err := s.service.Search(r.Context(), req)
    59  	if err != nil {
    60  		xhttp.WriteError(r.Context(), w, fmt.Errorf("failed to search: %s", err))
    61  		return
    62  	}
    63  
    64  	data, err := json.Marshal(result)
    65  	if err != nil {
    66  		xhttp.WriteInternalError(r.Context(), w, err)
    67  	}
    68  	w.Write(data)
    69  }
    70  
    71  func readSearchRequest(r *http.Request) (*Options, error) {
    72  	var err error
    73  
    74  	opts := &Options{}
    75  
    76  	opts.Query, err = xhttp.ReadQueryString(r, "query")
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	opts.Sort, err = xhttp.ReadQueryString(r, "sort")
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	opts.Page, err = xhttp.ReadQueryInt(r, "page")
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	opts.PageSize, err = xhttp.ReadQueryInt(r, "pageSize")
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	var filters []filterRequest
    97  	err = xhttp.ReadQueryJson(r, "filters", &filters)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  	opts.Filters, err = filtersFromFilterRequests(filters)
   102  
   103  	var aggregations map[string]aggregationRequest
   104  	err = xhttp.ReadQueryJson(r, "aggregations", &aggregations)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	opts.Aggregations, err = aggregationsFromAggregationRequests(aggregations)
   109  
   110  	return opts, nil
   111  }
   112  
   113  type filterRequest struct {
   114  	Type   string          `json:"type"`
   115  	Filter json.RawMessage `json:"filter"`
   116  }
   117  
   118  func filtersFromFilterRequests(filterRequests []filterRequest) ([]Filter, error) {
   119  	var filters []Filter
   120  	for _, filterRequest := range filterRequests {
   121  		switch filterRequest.Type {
   122  		case "terms":
   123  			var filter TermsFilter
   124  			err := json.Unmarshal(filterRequest.Filter, &filter)
   125  			if err != nil {
   126  				return nil, err
   127  			}
   128  			filters = append(filters, &filter)
   129  		case "daterange":
   130  			var filter DateRangeFilter
   131  			err := json.Unmarshal(filterRequest.Filter, &filter)
   132  			if err != nil {
   133  				return nil, err
   134  			}
   135  			filters = append(filters, &filter)
   136  		case "bool":
   137  			var filter BoolFilter
   138  			err := json.Unmarshal(filterRequest.Filter, &filter)
   139  			if err != nil {
   140  				return nil, err
   141  			}
   142  			filters = append(filters, &filter)
   143  		}
   144  	}
   145  	return filters, nil
   146  }
   147  
   148  type aggregationRequest struct {
   149  	Type    string          `json:"type"`
   150  	Field   string          `json:"field"`
   151  	Filters []filterRequest `json:"filters"`
   152  }
   153  
   154  func aggregationsFromAggregationRequests(aggregationRequests map[string]aggregationRequest) (map[string]Aggregation, error) {
   155  	aggregations := make(map[string]Aggregation, len(aggregationRequests))
   156  	for key, aggregationRequest := range aggregationRequests {
   157  		filters, err := filtersFromFilterRequests(aggregationRequest.Filters)
   158  		if err != nil {
   159  			return nil, err
   160  		}
   161  		switch aggregationRequest.Type {
   162  		case "terms":
   163  			aggregations[key] = Aggregation{
   164  				Type:    TermsAggregation,
   165  				Field:   aggregationRequest.Field,
   166  				Filters: filters,
   167  			}
   168  		case "daterange":
   169  			aggregations[key] = Aggregation{
   170  				Type:    DateRangeAggregation,
   171  				Field:   aggregationRequest.Field,
   172  				Filters: filters,
   173  			}
   174  		}
   175  	}
   176  	return aggregations, nil
   177  }