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 }