diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb index fbb4f66..919da30 100644 --- a/app/controllers/api/v2/search_controller.rb +++ b/app/controllers/api/v2/search_controller.rb @@ -21,9 +21,10 @@ def offices def volunteering_opportunities if search_criteria_specified? && search_q_is_valid? - render json: volunteering_search_response(params[:q]) + query_type = params[:query_type].presence || "location" + volunteering_search_by_query_type(query_type) else - render status: :bad_request, json: missing_search_param_json + render status: :bad_request, json: missing_search_type_json end end @@ -54,7 +55,18 @@ def office_search_response(query) { match_type: normalised_location.nil? ? "fuzzy" : "exact", results: offices.map { |office| office_as_search_result_json(office) } } end - def volunteering_search_response(query) + def volunteering_search_by_query_type(query_type) + case query_type + when "location" + render json: volunteering_location_search_response(params[:q]) + when "member" + render json: volunteering_member_search_response(params[:q]) + else + render status: :bad_request, json: missing_search_param_json + end + end + + def volunteering_location_search_response(query) offices, normalised_location = OfficeSearch.by_location query, only_with_vacancies: true rescue OfficeSearch::UnknownLocationError { match_type: "unknown", results: [] } @@ -66,9 +78,21 @@ def volunteering_search_response(query) end } end + def volunteering_member_search_response(member_id) + member = Office.find_by(id: member_id, office_type: "member") + return { match_type: "unknown", results: [] } if member.nil? + + offices_with_vacancies = member.children.where.not(volunteer_roles: []) + { match_type: "member", results: offices_with_vacancies.map { |office| volunteering_opportunity_as_search_result_json(office) } } + end + def missing_search_param_json { type: "https://local-office-search.citizensadvice.org.uk/schemas/v2/errors#missing-param", status: 400, title: "Required parameter (q) missing" } end + + def missing_search_type_json + { type: "https://local-office-search.citizensadvice.org.uk/schemas/v2/errors#missing-param", status: 400, title: "Required parameter (query_type) set to invalid value" } + end end end end diff --git a/app/controllers/api/v2/serialisers.rb b/app/controllers/api/v2/serialisers.rb index a0f3bbf..8051a0a 100644 --- a/app/controllers/api/v2/serialisers.rb +++ b/app/controllers/api/v2/serialisers.rb @@ -43,13 +43,14 @@ def volunteering_opportunity_as_json(office) } end - def volunteering_opportunity_as_search_result_json(office, distance_from) - { + def volunteering_opportunity_as_search_result_json(office, distance_from = nil) + result = { id: office.id, office: office_as_search_result_json(office), - roles: office.volunteer_roles, - distance: distance_in_miles(distance_from, office.location) + roles: office.volunteer_roles } + result[:distance] = distance_in_miles(distance_from, office.location) unless distance_from.nil? + result end private diff --git a/spec/requests/v2/schema.rb b/spec/requests/v2/schema.rb index 7a70c7c..70a55ac 100644 --- a/spec/requests/v2/schema.rb +++ b/spec/requests/v2/schema.rb @@ -171,7 +171,7 @@ module ApiV2Schema distance: { type: %i[number null] }, roles: VOLUNTEER_ROLES }, - required: %i[id office distance roles], + required: %i[id office roles], additionalProperties: false }.freeze @@ -180,9 +180,10 @@ module ApiV2Schema "$id": "https://local-office-search.citizensadvice.org.uk/schemas/v2/results/volunteering-opportunity", type: :object, properties: { - match_type: { type: :string, enum: %w[all exact fuzzy unknown out_of_area_scotland out_of_area_ni], + match_type: { type: :string, enum: %w[member exact fuzzy unknown out_of_area_scotland out_of_area_ni], description: %( - * `all` means no search term was specified so all volunteering opportunities are returned. + * `member` means the search terms matched a member ID and the results are all the results for this + member (might be none). * `exact` means the search term matched an exact location, so volunteering opportunities are ordered around this location * `fuzzy` means the search term matched a wider locality and not an individual point, so the results may diff --git a/spec/requests/v2/search_spec.rb b/spec/requests/v2/search_spec.rb index 03988e0..1e68573 100644 --- a/spec/requests/v2/search_spec.rb +++ b/spec/requests/v2/search_spec.rb @@ -192,12 +192,20 @@ produces "application/json" parameter name: :q, in: :query, type: :string, required: true, description: "the search terms to use" + parameter name: :query_type, in: :query, required: true, + enum: { + location: "treat q as a location to search around", + member: "treat q as a member ID to return all the opportunities for" + }, + default: "location", + description: "the search terms to use" response "200", "a list of search results when a query string is specified" do schema ApiV2Schema::VOLUNTEERING_SEARCH_RESULTS context "when a fuzzy search is done" do let(:q) { "Testshire" } + let(:query_type) { "location" } let(:office) do Office.new id: generate_salesforce_id, @@ -217,6 +225,7 @@ context "when there are offices with no volunteering opportunities" do let(:q) { "Testshire" } + let(:query_type) { "location" } let(:office) do Office.new id: generate_salesforce_id, @@ -237,6 +246,7 @@ context "when an exact search is done" do let(:q) { "AA1 1AA" } + let(:query_type) { "location" } let(:nearest_office) do Office.new(id: generate_salesforce_id, @@ -266,12 +276,60 @@ expect_result_ids_in_response response, "exact", [nearest_office.id, far_office.id] end end + + context "when a search by member is done and results match" do + let(:q) { member.id } + let(:query_type) { "member" } + + let(:member) do + Office.new(id: generate_salesforce_id, + office_type: :member, + name: "Testshire Citizens Advice") + end + + let(:member_office) do + Office.new(id: generate_salesforce_id, + parent_id: member.id, + office_type: :office, + name: "Testshire Citizens Advice", + volunteer_roles: ["admin_and_customer_service"], + location: "POINT(2.0 2.0)") + end + + before do + member.save! + member_office.save! + other_member = Office.create(id: generate_salesforce_id, + office_type: :member, + name: "Othertown Citizens Advice") + Office.create(id: generate_salesforce_id, + parent_id: other_member.id, + office_type: :office, + name: "Othertown Citizens Advice", + volunteer_roles: ["admin_and_customer_service"], + location: "POINT(3.0 3.0)") + end + + run_test! do |response| + expect_result_ids_in_response response, "member", [member_office.id] + end + end + + context "when a search by member is done and there is no member with that ID" do + let(:q) { generate_salesforce_id } + let(:query_type) { "member" } + + run_test! do |response| + expect_result_ids_in_response response, "unknown", [] + end + end end response "400", "If query is not specified" do schema ApiV2Schema::JSON_PROBLEM let(:q) { "" } + let(:query_type) { "location" } run_test! end diff --git a/swagger/v2/swagger.yaml b/swagger/v2/swagger.yaml index 552a4d1..b91688c 100644 --- a/swagger/v2/swagger.yaml +++ b/swagger/v2/swagger.yaml @@ -548,6 +548,16 @@ paths: description: the search terms to use schema: type: string + - name: query_type + in: query + required: true + enum: + location: treat q as a location to search around + member: treat q as a member ID to return all the opportunities for + default: location + description: "the search terms to use:\n * `location` treat q as a location + to search around\n * `member` treat q as a member ID to return all the opportunities + for\n " responses: '200': description: a list of search results when a query string is specified @@ -561,17 +571,17 @@ paths: match_type: type: string enum: - - all + - member - exact - fuzzy - unknown - out_of_area_scotland - out_of_area_ni - description: "\n * `all` means - no search term was specified so all volunteering opportunities - are returned.\n * `exact` means - the search term matched an exact location, so volunteering opportunities - are ordered around\n this + description: "\n * `member` means + the search terms matched a member ID and the results are all + the results for this member (might be none).\n * + `exact` means the search term matched an exact location, so + volunteering opportunities are ordered around\n this location\n * `fuzzy` means the search term matched a wider locality and not an individual point, so the results may\n only @@ -652,7 +662,6 @@ paths: required: - id - office - - distance - roles additionalProperties: false required: