diff --git a/app/api/srch/search.rb b/app/api/srch/search.rb index 12f034eeb9..e647e3ae86 100644 --- a/app/api/srch/search.rb +++ b/app/api/srch/search.rb @@ -115,6 +115,29 @@ class Search < Grape::API sresult end + # Request URL should be /api/srch/locations?srchString=QRY[&seq=KEYCOUNT&showCount=NUM_ROWS&pageNum=PAGE_NUM] + # Note: Query(QRY as above) must have latitude and longitude as srchString=lat,lon + desc 'Perform a search of documents having nearby latitude and longitude tag values', + hidden: false, + is_array: false, + nickname: 'srchGetLocations' + params do + requires :srchString, type: String, documentation: { example: 'Spec' } + optional :seq, type: Integer, documentation: { example: 995 } + optional :showCount, type: Integer, documentation: { example: 3 } + optional :pageNum, type: Integer, documentation: { example: 0 } + end + get :locations do + sresult = DocList.new + unless params[:srchString].nil? || params[:srchString] == 0 || !(params[:srchString].include? ",") + sservice = SearchService.new + sresult = sservice.nearbyNodes(params[:srchString]) + end + sparms = SearchRequest.fromRequest(params) + sresult.srchParams = sparms + sresult + end + # end endpoint definitions end end diff --git a/app/models/doc_result.rb b/app/models/doc_result.rb index 5b30a57a63..1ceb188432 100644 --- a/app/models/doc_result.rb +++ b/app/models/doc_result.rb @@ -1,6 +1,6 @@ # A DocResult is an individual return item for a document (web page) search class DocResult - attr_accessor :docId, :docType, :docUrl, :docTitle, :docSummary, :docScore + attr_accessor :docId, :docType, :docUrl, :docTitle, :docSummary, :docScore, :latitude, :longitude, :blurred def initialize; end @@ -15,6 +15,20 @@ def self.fromSearch(idval, typeval, urlval, titleval, sumval, scoreval) obj end + def self.fromLocationSearch(idval, typeval, urlval, titleval, sumval, scoreval, latitude, longitude, blurred) + obj = new + obj.docId = idval + obj.docType = typeval + obj.docUrl = urlval + obj.docTitle = titleval + obj.docSummary = sumval + obj.docScore = scoreval + obj.latitude = latitude + obj.longitude = longitude + obj.blurred = blurred + obj + end + # This subclass is used to auto-generate the RESTful data structure. It is generally not useful for internal Ruby usage # but must be included for full RESTful functionality. class Entity < Grape::Entity @@ -24,5 +38,8 @@ class Entity < Grape::Entity expose :docTitle, documentation: { type: 'String', desc: 'Title or primary descriptor of the linked result.' } expose :docSummary, documentation: { type: 'String', desc: 'If available, first paragraph or descriptor of the linked document.' } expose :docScore, documentation: { type: 'Float', desc: "If calculated, the relevance of the document result to the search request; i.e. the 'matching score'" } + expose :latitude, documentation: { type: 'String', desc: "Returns the latitude associated with the node." } + expose :longitude, documentation: { type: 'String', desc: "Returns the longitude associated with the node." } end + end diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 5673bd1bd0..b34dbc1b0e 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -183,4 +183,40 @@ def textSearch_questions(srchString) end sresult end + + # Search nearby nodes with respect to given latitude and longitude + def nearbyNodes(srchString) + sresult = DocList.new + coordinates = srchString.split(",") + lat = coordinates[0] + lon = coordinates[1] + + nids = NodeTag.joins(:tag) + .where('name LIKE ?', 'lat:' + lat[0..lat.length - 2] + '%') + .collect(&:nid) + + nids = nids || [] + + items = Node.includes(:tag) + .references(:node, :term_data) + .where('node.nid IN (?) AND term_data.name LIKE ?', nids, 'lon:' + lon[0..lon.length - 2] + '%') + .limit(200) + .order('node.nid DESC') + + items.each do |match| + blurred = false + + match.node_tags.each do |tag| + if tag.name == "location:blurred" + blurred = true + break + end + end + + doc = DocResult.fromLocationSearch(match.nid, 'coordinates', match.path(:items), match.title, 0, match.answers.length.to_i, match.lat, match.lon, blurred) + sresult.addDoc(doc) + end + sresult + end + end diff --git a/doc/API.md b/doc/API.md index 673a8a0458..7256dfab3b 100644 --- a/doc/API.md +++ b/doc/API.md @@ -10,6 +10,7 @@ Per-model API endpoints are: * Questions: https://publiclab.org/api/srch/questions?srchString=foo * Tags: https://publiclab.org/api/srch/tags?srchString=foo * Notes: https://publiclab.org/api/srch/notes?srchString=foo +* Locations: https://publiclab.org/api/srch/locations?srchString=lat,lon We also provide RSS feeds for tags and authors, in the format: diff --git a/test/functional/search_api_test.rb b/test/functional/search_api_test.rb index 496cb6312d..1c7695906c 100644 --- a/test/functional/search_api_test.rb +++ b/test/functional/search_api_test.rb @@ -10,7 +10,6 @@ def app test 'search notes functionality' do get '/api/srch/notes?srchString=Blog' assert last_response.ok? - # Expected search pattern pattern = { srchParams: { @@ -26,7 +25,31 @@ def app assert_equal nodes(:blog).path, json['items'][0]['docUrl'] assert_equal "Blog post", json['items'][0]['docTitle'] assert_equal 13, json['items'][0]['docId'] - + + assert matcher =~ json + + end + + test 'search nearby nodes functionality' do + get '/api/srch/locations?srchString=71.00,52.00' + assert last_response.ok? + + # Expected search pattern + pattern = { + srchParams: { + srchString: '71.00,52.00', + seq: nil, + }.ignore_extra_keys! + }.ignore_extra_keys! + + matcher = JsonExpressions::Matcher.new(pattern) + + json = JSON.parse(last_response.body) + + assert_equal nodes(:blog).path, json['items'][0]['docUrl'] + assert_equal "Blog post", json['items'][0]['docTitle'] + assert_equal 13, json['items'][0]['docId'] + assert matcher =~ json end