├── log └── .gitkeep ├── .rspec ├── .ruby-version ├── lib ├── tasks │ ├── .gitkeep │ └── cucumber.rake └── assets │ └── .gitkeep ├── public ├── favicon.ico ├── robots.txt ├── 500.html ├── 422.html └── 404.html ├── test ├── unit │ └── .gitkeep ├── fixtures │ └── .gitkeep ├── functional │ ├── .gitkeep │ └── datasets_controller_test.rb ├── integration │ └── .gitkeep ├── performance │ └── browsing_test.rb └── test_helper.rb ├── app ├── mailers │ └── .gitkeep ├── models │ ├── .gitkeep │ ├── holding.rb │ ├── token.rb │ ├── health.rb │ ├── client_id.rb │ └── ceos_agency.rb ├── helpers │ ├── home_helper.rb │ └── collections_helper.rb ├── assets │ ├── images │ │ ├── rails.png │ │ ├── ui-icons_217bc0_256x240.png │ │ ├── ui-icons_2e83ff_256x240.png │ │ ├── ui-icons_469bdd_256x240.png │ │ ├── ui-icons_6da8d5_256x240.png │ │ ├── ui-icons_cd0a0a_256x240.png │ │ ├── ui-icons_d8e7f3_256x240.png │ │ ├── ui-icons_f9bd01_256x240.png │ │ ├── cmr-opensearch-logo-small.png │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ ├── ui-bg_flat_55_fbec88_40x100.png │ │ ├── ui-bg_glass_75_d0e5f5_1x400.png │ │ ├── ui-bg_glass_85_dfeffc_1x400.png │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ ├── cmr-opensearch-logo-small-hover.png │ │ ├── ui-bg_inset-hard_100_f5f8f9_1x100.png │ │ ├── ui-bg_inset-hard_100_fcfdfd_1x100.png │ │ └── ui-bg_gloss-wave_55_5c9ccc_500x100.png │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ └── fontawesome-webfont.woff │ ├── stylesheets │ │ ├── images │ │ │ └── rails.png │ │ ├── datetimepicker.css │ │ ├── home.css.scss │ │ ├── granules.css.scss │ │ ├── collections.css.scss │ │ ├── scaffolds.css.scss │ │ ├── application.css.scss │ │ ├── results.css.scss │ │ └── resource.css │ └── javascripts │ │ ├── home.js │ │ ├── collections.js │ │ ├── results.js │ │ ├── granules.js │ │ ├── application.js │ │ └── jquery-ui-sliderAccess.js ├── controllers │ ├── home_controller.rb │ ├── health_controller.rb │ ├── holdings_controller.rb │ └── application_controller.rb ├── views │ ├── granules │ │ ├── ghrsst.xml.erb │ │ ├── inpe.xml.erb │ │ ├── nrscc.xml.erb │ │ ├── error.xml.erb │ │ ├── nrsc.xml.erb │ │ ├── usgslsi.xml.erb │ │ └── eumetsat.xml.erb │ ├── shared │ │ ├── _result_metrics.html.erb │ │ ├── _errors.html.erb │ │ ├── _pagination.html.erb │ │ └── _result.html.erb │ └── layouts │ │ └── application.html.erb ├── jobs │ ├── job_base.rb │ └── memory_profiler.rb └── services │ ├── place_name_to_point.rb │ ├── well_formed_text.rb │ └── connection_verifier.rb ├── vendor ├── plugins │ └── .gitkeep └── assets │ ├── javascripts │ └── .gitkeep │ └── stylesheets │ └── .gitkeep ├── .ruby-gemset ├── Procfile ├── config ├── secret_token.yml.template ├── errbit.yml.template ├── environment.rb ├── boot.rb ├── initializers │ ├── mime_types.rb │ ├── backtrace_silencers.rb │ ├── session_store.rb │ ├── quiet_assets.rb.old │ ├── wrap_parameters.rb │ ├── inflections.rb │ ├── secret_token.rb │ └── rufus_scheduled_tasks.rb ├── locales │ └── en.yml ├── cucumber.yml ├── database.yml ├── application.yml.template ├── environments │ ├── development.rb │ ├── test.rb │ ├── sit.rb │ ├── uat.rb │ └── production.rb └── routes.rb ├── .dockerignore ├── docker-compose.yml ├── doc └── README_FOR_APP ├── config.ru ├── .travis.yml ├── spec ├── controllers │ ├── home_controller_spec.rb │ ├── health_controller_spec.rb │ └── collections_controller_spec.rb ├── models │ ├── client_id_spec.rb │ ├── health_spec.rb │ └── ceos_agency_spec.rb ├── support │ └── vcr_setup.rb ├── views │ ├── collections │ │ ├── invalid_place_name_spec.rb │ │ ├── ceos_collections_spec.rb │ │ ├── provider_search_spec.rb │ │ ├── ceos_bp_1.2_compliance_spec.rb │ │ ├── geoss_collections_tagging_spec.rb │ │ └── eosdis_collections_tagging_spec.rb │ └── granules │ │ ├── parent_identifier_spec.rb │ │ ├── data_links_spec.rb │ │ ├── ceos_bp_1.2_compliance_spec.rb │ │ ├── required_form_params_spec.rb │ │ └── esip_bp_up_link_spec.rb ├── services │ ├── place_name_to_geometry_spec.rb │ ├── well_formed_text_spec.rb │ └── eosdis_tagger_spec.rb ├── fixtures │ ├── models │ │ └── health │ │ │ ├── cmr_bad.yml │ │ │ ├── cmr_good.yml │ │ │ └── cmr_bad_status.yml │ ├── views │ │ ├── granule │ │ │ ├── navigation_95268_offset.yml │ │ │ └── navigation_95269_offset.yml │ │ └── collection │ │ │ ├── navigation_32287_offset.yml │ │ │ └── navigation_32288_offset.yml │ ├── services │ │ └── place_name_to_geometry.yml │ └── controllers │ │ ├── granules_cursor_too_large.yml │ │ ├── datasets_cursor_too_large.yml │ │ └── granules │ │ └── graphql │ │ ├── invalid.yml │ │ └── C1220566843-USGS_LTA.yml ├── spec_helper.rb └── helpers │ └── granules_helpers_spec.rb ├── script ├── rails └── cucumber ├── db ├── seeds.rb └── schema.rb ├── .project ├── Rakefile ├── features ├── home │ ├── home.feature │ └── step_definitions │ │ └── home_steps.rb ├── collections │ ├── html │ │ ├── search_by_placename.feature │ │ ├── search_by_uid.feature │ │ ├── search_by_granules_only.feature │ │ ├── search_by_cwic_only.feature │ │ └── search_by_geoss_only.feature │ └── atom │ │ ├── search_to_geoss_dataset.feature │ │ ├── search_to_collection_specific_granule_osdd_link.feature │ │ ├── search_by_cwic_only.feature │ │ ├── search_by_geoss_only.feature │ │ ├── search_by_placename.feature │ │ ├── search_by_granules_only.feature │ │ ├── search_by_uid.feature │ │ ├── search_to_cwic_dataset.feature │ │ ├── search_dc_temporal.feature │ │ └── search_validation.feature ├── granules │ ├── html │ │ ├── search_by_placename.feature │ │ └── search_by_uid.feature │ └── atom │ │ ├── search_by_uid.feature │ │ ├── search_by_dataset_id.feature │ │ ├── search_by_placename.feature │ │ ├── search_dc_temporal.feature │ │ └── search_validation.feature ├── support │ ├── vcr.rb │ ├── cuke_env.rb │ └── env.rb ├── opensearch_descriptor_documents │ ├── collections.feature │ └── granules.feature └── fixtures │ └── cucumber_tags │ ├── datasets_search_validation_atom_old.yml │ └── granules_search_validation_atom_old.yml ├── .gitignore ├── Dockerfile └── Gemfile /log/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.1.4 -------------------------------------------------------------------------------- /lib/tasks/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/mailers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/functional/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | cmr-opensearch 2 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec unicorn -p 15400 -E $RAILS_ENV -------------------------------------------------------------------------------- /app/helpers/home_helper.rb: -------------------------------------------------------------------------------- 1 | module HomeHelper 2 | end 3 | -------------------------------------------------------------------------------- /config/secret_token.yml.template: -------------------------------------------------------------------------------- 1 | secret_token: @OPEN_SEARCH_SECRET_TOKEN@ 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | git* 2 | log/* 3 | tmp/* 4 | Dockerfile 5 | README.rdoc 6 | README.md 7 | CONTRIBUTING.md 8 | -------------------------------------------------------------------------------- /app/assets/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/rails.png -------------------------------------------------------------------------------- /app/assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /app/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /app/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /app/assets/stylesheets/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/stylesheets/images/rails.png -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | def index; end 3 | 4 | def docs; end 5 | end 6 | -------------------------------------------------------------------------------- /config/errbit.yml.template: -------------------------------------------------------------------------------- 1 | errbit_host: @ERRBIT_HOST@ 2 | errbit_port: @ERRBIT_PORT@ 3 | errbit_api_key: @OPENSEARCH_ERRBIT_API_KEY@ 4 | -------------------------------------------------------------------------------- /app/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /app/assets/images/ui-icons_217bc0_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/ui-icons_217bc0_256x240.png -------------------------------------------------------------------------------- /app/assets/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /app/assets/images/ui-icons_469bdd_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/ui-icons_469bdd_256x240.png -------------------------------------------------------------------------------- /app/assets/images/ui-icons_6da8d5_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/ui-icons_6da8d5_256x240.png -------------------------------------------------------------------------------- /app/assets/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /app/assets/images/ui-icons_d8e7f3_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/ui-icons_d8e7f3_256x240.png -------------------------------------------------------------------------------- /app/assets/images/ui-icons_f9bd01_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/ui-icons_f9bd01_256x240.png -------------------------------------------------------------------------------- /app/models/holding.rb: -------------------------------------------------------------------------------- 1 | class Holding 2 | def self.find(provider) 3 | Rails.cache.read("holdings-#{provider.downcase}") || {} 4 | end 5 | end -------------------------------------------------------------------------------- /app/assets/images/cmr-opensearch-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/cmr-opensearch-logo-small.png -------------------------------------------------------------------------------- /app/assets/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /app/assets/images/ui-bg_flat_55_fbec88_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/ui-bg_flat_55_fbec88_40x100.png -------------------------------------------------------------------------------- /app/assets/images/ui-bg_glass_75_d0e5f5_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/ui-bg_glass_75_d0e5f5_1x400.png -------------------------------------------------------------------------------- /app/assets/images/ui-bg_glass_85_dfeffc_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/ui-bg_glass_85_dfeffc_1x400.png -------------------------------------------------------------------------------- /app/assets/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /app/assets/images/cmr-opensearch-logo-small-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/cmr-opensearch-logo-small-hover.png -------------------------------------------------------------------------------- /app/assets/images/ui-bg_inset-hard_100_f5f8f9_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/ui-bg_inset-hard_100_f5f8f9_1x100.png -------------------------------------------------------------------------------- /app/assets/images/ui-bg_inset-hard_100_fcfdfd_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/ui-bg_inset-hard_100_fcfdfd_1x100.png -------------------------------------------------------------------------------- /app/assets/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa/cmr-opensearch/master/app/assets/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png -------------------------------------------------------------------------------- /app/assets/javascripts/home.js: -------------------------------------------------------------------------------- 1 | // Place all the behaviors and hooks related to the matching controller here. 2 | // All this logic will automatically be available in application.js. 3 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | EchoOpensearch::Application.initialize! 6 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | web: 4 | build: . 5 | command: bundle exec rails s -p 3000 -b '0.0.0.0' 6 | volumes: 7 | - .:/cmr-opensearch 8 | ports: 9 | - "3000:3000" 10 | -------------------------------------------------------------------------------- /doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | User-Agent: * 5 | Disallow: / 6 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'logger' 3 | 4 | # Set up gems listed in the Gemfile. 5 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 6 | 7 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 8 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | 5 | map '/opensearch' do 6 | run EchoOpensearch::Application 7 | end 8 | #run EchoOpensearch::Application 9 | -------------------------------------------------------------------------------- /test/functional/datasets_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DatasetsControllerTest < ActionController::TestCase 4 | test "should get descriptor_document" do 5 | get :descriptor_document 6 | assert_response :success 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | cache: 3 | bundler: true 4 | sudo: false 5 | script: 6 | - export RAILS_ENV=test 7 | - bundle exec rspec 8 | - bundle exec cucumber 9 | branches: 10 | only: # Only build master. Pull requests to master also get built. 11 | - master -------------------------------------------------------------------------------- /spec/controllers/home_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe HomeController do 4 | 5 | describe "GET 'index'" do 6 | it "returns http success" do 7 | get 'index' 8 | expect(response.status).to eq(200) 9 | end 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /app/helpers/collections_helper.rb: -------------------------------------------------------------------------------- 1 | module CollectionsHelper 2 | def truncate_link(link) 3 | title = link.title ? link.title : 'undefined' 4 | markup = "#{title.truncate(64)}" 5 | markup.html_safe 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /app/assets/javascripts/collections.js: -------------------------------------------------------------------------------- 1 | // Place all the behaviors and hooks related to the matching controller here. 2 | // All this logic will automatically be available in application.js. 3 | //= require jquery-1.7.2.min 4 | //= require jquery-ui-1.8.20.custom.min 5 | //= require jquery-ui-sliderAccess 6 | //= require jquery-ui-timepicker-addon 7 | //= require results -------------------------------------------------------------------------------- /app/views/granules/ghrsst.xml.erb: -------------------------------------------------------------------------------- 1 | 2 | 4 | PLACEHOLDER 5 | Collection Specific Granule OSDDs are not supported for this provider. 6 | <%= ENV['contact']%> 7 | -------------------------------------------------------------------------------- /app/views/granules/inpe.xml.erb: -------------------------------------------------------------------------------- 1 | 2 | 4 | PLACEHOLDER 5 | Collection Specific Granule OSDDs are not supported for this provider. 6 | <%= ENV['contact']%> 7 | -------------------------------------------------------------------------------- /app/views/granules/nrscc.xml.erb: -------------------------------------------------------------------------------- 1 | 2 | 4 | PLACEHOLDER 5 | Collection Specific Granule OSDDs are not supported for this provider. 6 | <%= ENV['contact']%> 7 | -------------------------------------------------------------------------------- /script/cucumber: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | vendored_cucumber_bin = Dir["#{File.dirname(__FILE__)}/../vendor/{gems,plugins}/cucumber*/bin/cucumber"].first 4 | if vendored_cucumber_bin 5 | load File.expand_path(vendored_cucumber_bin) 6 | else 7 | require 'rubygems' unless ENV['NO_RUBYGEMS'] 8 | require 'cucumber' 9 | load Cucumber::BINARY 10 | end 11 | -------------------------------------------------------------------------------- /app/views/shared/_result_metrics.html.erb: -------------------------------------------------------------------------------- 1 |
2 | Displaying <%= start_hit(cursor, number_of_results, number_of_hits) %> to <%= stop_hit(cursor, length, number_of_results, number_of_hits) %> of <%= number_of_hits %> 3 | <% unless parent.nil? %> 4 | granules for collection '<%= parent %>' 5 | <% end %> 6 | (took <%= time %> seconds) 7 |
-------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | echo-opensearch 4 | 5 | 6 | 7 | 8 | 9 | 10 | com.aptana.projects.webnature 11 | org.radrails.rails.core.railsnature 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/views/shared/_errors.html.erb: -------------------------------------------------------------------------------- 1 | <% if errors.any? %> 2 |
3 |

<%= pluralize(errors.count, "error") %> prohibited this search from being executed:

4 | 9 |
10 | <% end %> -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | activemodel: 6 | attributes: 7 | granule: 8 | parentIdentifier: "Collection concept ID" 9 | uid: "Unique ID" 10 | shortName: "Short name" -------------------------------------------------------------------------------- /app/controllers/health_controller.rb: -------------------------------------------------------------------------------- 1 | class HealthController < ApplicationController 2 | respond_to :json 3 | 4 | def index 5 | health = Health.new 6 | response = "{\"cmr-search\":{\"ok?\":#{health.ok?}}}" 7 | respond_to do |format| 8 | format.json do 9 | render :plain => response, :status => health.ok? ? :ok : :service_unavailable 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/jobs/job_base.rb: -------------------------------------------------------------------------------- 1 | class JobBase 2 | 3 | def run() 4 | begin 5 | execute 6 | rescue => exception 7 | Rails.logger.error("Caught exception in scheduled job #{self.class.name}: #{exception.message}\n#{exception.inspect}\n#{exception.backtrace.join("\n")}\n") 8 | ensure 9 | ActiveRecord::Base.connection_pool.release_connection if ActiveRecord::Base.connection_pool.active_connection? 10 | end 11 | end 12 | 13 | end -------------------------------------------------------------------------------- /test/performance/browsing_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rails/performance_test_help' 3 | 4 | class BrowsingTest < ActionDispatch::PerformanceTest 5 | # Refer to the documentation for all available options 6 | # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory] 7 | # :output => 'tmp/performance', :formats => [:flat] } 8 | 9 | def test_homepage 10 | get '/' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/assets/stylesheets/datetimepicker.css: -------------------------------------------------------------------------------- 1 | .ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } 2 | .ui-timepicker-div dl { text-align: left; } 3 | .ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; } 4 | .ui-timepicker-div dl dd { margin: 0 10px 10px 65px; } 5 | .ui-timepicker-div td { font-size: 90%; } 6 | .ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } 7 | div#ui-datepicker-div { 8 | width: 30em; 9 | } -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | EchoOpensearch::Application.config.session_store :cookie_store, key: '_echo-opensearch_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # EchoOpensearch::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /app/models/token.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | class Token 4 | 5 | def self.get(base64_creds) 6 | response = RestClient.post "#{ENV['edl_endpoint']}/token", {}, {:Authorization => "Basic #{base64_creds}"} 7 | 8 | token_id = JSON.parse(response.body)["access_token"] 9 | return token_id 10 | end 11 | 12 | def self.delete(token_id) 13 | RestClient.delete "#{ENV['edl_endpoint']}/revoke_token?token=#{token_id}", {:Authorization => "Basic #{base64_creds}"} 14 | end 15 | end -------------------------------------------------------------------------------- /config/initializers/quiet_assets.rb.old: -------------------------------------------------------------------------------- 1 | if Rails.env.development? 2 | Rails.application.assets.logger = Logger.new('/dev/null') 3 | Rails::Rack::Logger.class_eval do 4 | def call_with_quiet_assets(env) 5 | previous_level = Rails.logger.level 6 | Rails.logger.level = Logger::ERROR if env['PATH_INFO'] =~ %r{^/assets/} 7 | call_without_quiet_assets(env) 8 | ensure 9 | Rails.logger.level = previous_level 10 | end 11 | alias_method_chain :call, :quiet_assets 12 | end 13 | end -------------------------------------------------------------------------------- /config/cucumber.yml: -------------------------------------------------------------------------------- 1 | <% 2 | rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" 3 | rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" 4 | std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags 'not @wip' -r features" 5 | %> 6 | default: <%= std_opts %> features --publish-quiet 7 | wip: --tags @wip:3 --wip features 8 | rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags 'not @wip' 9 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. 7 | # 8 | # Note: You'll currently still have to declare fixtures explicitly in integration tests 9 | # -- they do not yet inherit this setting 10 | fixtures :all 11 | 12 | # Add more helper methods to be used by all tests here... 13 | end 14 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # Disable root element in JSON by default. 12 | ActiveSupport.on_load(:active_record) do 13 | self.include_root_in_json = false 14 | end 15 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | require 'rake' 7 | 8 | EchoOpensearch::Application.load_tasks 9 | 10 | namespace :db do 11 | task :reset do 12 | puts 'Nothing to see here' 13 | end 14 | task :setup do 15 | puts 'Nothing to see here' 16 | end 17 | 18 | task :migrate do 19 | puts 'Nothing to see here' 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/health.rb: -------------------------------------------------------------------------------- 1 | class Health 2 | # things to check: CMR search 3 | 4 | def initialize 5 | @cmr_ok = cmr_status 6 | end 7 | 8 | def ok? 9 | @cmr_ok 10 | end 11 | 12 | private 13 | 14 | def cmr_status 15 | ok = false 16 | response = nil 17 | begin 18 | response = RestClient::Request.execute :method => :get, :url => "#{ENV['catalog_rest_endpoint']}health", :verify_ssl => OpenSSL::SSL::VERIFY_NONE 19 | ok = response.body.include?("\"ok?\":true") && !response.body.include?(':false') 20 | rescue 21 | ok = false 22 | end 23 | ok 24 | end 25 | end -------------------------------------------------------------------------------- /app/assets/stylesheets/home.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | */ 13 | 14 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | # 12 | # These inflection rules are supported but not enabled by default: 13 | # ActiveSupport::Inflector.inflections do |inflect| 14 | # inflect.acronym 'RESTful' 15 | # end 16 | -------------------------------------------------------------------------------- /spec/models/client_id_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ClientId do 4 | describe "client id" do 5 | it "is possible to create a client id 'foo'" do 6 | c = ClientId.new({:clientId => 'foo'}) 7 | 8 | expect(c.valid?).to eq(true) 9 | end 10 | 11 | it "is possible to create a client id 'aw3'" do 12 | c = ClientId.new({:clientId => 'aw3'}) 13 | 14 | expect(c.valid?).to eq(true) 15 | end 16 | 17 | it "is not possible to create a client id '###'" do 18 | c = ClientId.new({:clientId => '###'}) 19 | expect(c.valid?).to eq(false) 20 | end 21 | end 22 | end -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem 'activerecord-jdbcsqlite3-adapter' 3 | # 4 | # Configure Using Gemfile 5 | # gem 'activerecord-jdbcsqlite3-adapter' 6 | # 7 | development: 8 | adapter: sqlite3 9 | database: db/development.sqlite3 10 | 11 | # Warning: The database defined as "test" will be erased and 12 | # re-generated from your development database when you run "rake". 13 | # Do not set this db to the same as development or production. 14 | test: &test 15 | adapter: sqlite3 16 | database: db/test.sqlite3 17 | 18 | production: 19 | adapter: sqlite3 20 | database: db/production.sqlite3 21 | 22 | cucumber: 23 | <<: *test -------------------------------------------------------------------------------- /app/models/client_id.rb: -------------------------------------------------------------------------------- 1 | class ClientId 2 | include ActiveModel::Validations 3 | include ActiveModel::Conversion 4 | extend ActiveModel::Naming 5 | 6 | attr_accessor :clientId 7 | 8 | validates :clientId, 9 | format: { 10 | with: /\A[a-zA-Z0-9_\-]+\z/, 11 | message: 'is invalid, it must be an alpha-numeric string' 12 | }, 13 | allow_blank: true 14 | 15 | def initialize(attributes = {}) 16 | attributes.each do |name, value| 17 | send("#{name}=", value) if %w[clientId].include? name.to_s 18 | end 19 | end 20 | 21 | def persisted? 22 | false 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/jobs/memory_profiler.rb: -------------------------------------------------------------------------------- 1 | class MemoryProfiler < JobBase 2 | def execute 3 | Rails.logger.info("Memory Stats Profile: #{GC.stat}") 4 | Rails.logger.info("Connection pool size: #{ActiveRecord::Base.connection_pool.connections.size}") 5 | Rails.logger.info("Starting Database Polling") 6 | #Rails.logger.info("Current Thread Connection Auto Commit: #{ActiveRecord::Base.connection.raw_connection.auto_commit}") 7 | ActiveRecord::Base.connection_pool.connections.each_with_index { |connection,number| Rails.logger.info("Connection Number:#{number + 1} Open Transactions:#{connection.open_transactions.to_s} Last Used: #{Time.now - connection.last_use}s ago")} 8 | end 9 | end -------------------------------------------------------------------------------- /app/assets/stylesheets/granules.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require jquery-ui-1.8.20.custom 13 | *= require datetimepicker 14 | *= require results 15 | */ -------------------------------------------------------------------------------- /app/assets/stylesheets/collections.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require jquery-ui-1.8.20.custom 13 | *= require datetimepicker 14 | *= require results 15 | */ 16 | 17 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | config_filename = File.join(File.dirname(File.expand_path(__FILE__)), '..', 'secret_token.yml') 2 | #secret_token_config = YAML.load_file(config_filename) 3 | 4 | # Be sure to restart your server when you modify this file. 5 | 6 | # Your secret key for verifying the integrity of signed cookies. 7 | # If you change this key, all old signed cookies will become invalid! 8 | # Make sure the secret is at least 30 characters and all random, 9 | # no regular words or you'll be exposed to dictionary attacks. 10 | EchoOpensearch::Application.config.secret_key_base = '69178fcac03379251a9b3a0a75c237a5960cb3cce6746477988199eb2cc9be01720a50d3d12121dc3367359a4eb7e56feb3b44033f3609c8bdb61ae830b4206f' 11 | -------------------------------------------------------------------------------- /spec/support/vcr_setup.rb: -------------------------------------------------------------------------------- 1 | VCR.configure do |c| 2 | # By default VCR will intercept all http calls. We don't want it to intercept echo-access calls, just catalog-rest and echo-rest. 3 | c.ignore_hosts '127.0.0.1', 'localhost' 4 | # The directory where your cassettes will be saved 5 | c.cassette_library_dir = 'spec/fixtures' 6 | #c.preserve_exact_body_bytes do |http_message| 7 | # http_message.body.encoding.name == 'UTF-8' || 8 | # !http_message.body.valid_encoding? 9 | #end 10 | # force UTF 8 encoding since binary cannot be correctly decoded in some cases 11 | #c.before_record do |i| 12 | # i.response.body.force_encoding('UTF-8') #'UTF-8' or 'ASCII-8BIT' 13 | #end 14 | c.hook_into :webmock 15 | end -------------------------------------------------------------------------------- /spec/views/collections/invalid_place_name_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'collections searches with an invalid place name' do 4 | include Capybara::DSL 5 | 6 | it 'unsupported place name is handled correctly' do 7 | VCR.use_cassette 'views/collection/invalid_place_name', :decode_compressed_response => true, :record => :once do 8 | visit collections_path 9 | select("Place Name", :from => 'spatial_type') 10 | fill_in("placeName", :with => 'dougopolis') 11 | click_button('Search') 12 | expect(page).to have_content("1 error prohibited this search from being executed:") 13 | expect(page).to have_content("Placename dougopolis cannot be located") 14 | end 15 | end 16 | end -------------------------------------------------------------------------------- /app/views/granules/error.xml.erb: -------------------------------------------------------------------------------- 1 | 8 | OpenSearch Exception 9 | <%= @error %> 10 | <%= DateTime.now.utc.iso8601() %> 11 | <%= request.original_url %> 12 | 13 | 0 14 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /features/home/home.feature: -------------------------------------------------------------------------------- 1 | Feature: Retrieve home page 2 | In order to obtain dataset and granule products 3 | as an open search user 4 | I should be able to navigate to the open search home page 5 | 6 | Scenario: View Home Page 7 | Given I am on the open search home page 8 | Then I should see a page title reading "CMR OpenSearch" 9 | And I should see "CMR provides a search interface based on the OpenSearch concept in general and the ESIP (Earth Science Information Partners) federated search concept in particular." 10 | And I should see the subtitle "Collections" 11 | And I should see the subtitle "Granules" 12 | And I should see "NASA Official: Doug Newman" 13 | And I should see the current version of CMR 14 | And I should see a link to the CMR OpenSearch release documentation -------------------------------------------------------------------------------- /features/collections/html/search_by_placename.feature: -------------------------------------------------------------------------------- 1 | @datasets_search_by_placename_html 2 | Feature: Retrieve collections in html format 3 | In order to obtain granule products 4 | as an open search user 5 | I should be able to specify a place name as spatial search constraint 6 | 7 | Scenario: Search for collection using placename 8 | Given I have executed a html collection search with the following parameters: 9 | | input | value | 10 | | spatial_type | Place Name | 11 | | placeName | Bowness-on-Solway | 12 | Then I should see 1 collection result 13 | And collection result 1 should have a the following echo characteristics, 14 | | characteristic | value | 15 | | short_name | BownessOnSolwayDataset | 16 | -------------------------------------------------------------------------------- /app/services/place_name_to_point.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | class PlaceNameToPoint 4 | URL = 'http://api.geonames.org/searchJSON?' 5 | FIXED_PARAMETERS = '&username=echo_reverb&maxRows=1' 6 | 7 | def self.exists? place_name 8 | find(place_name) 9 | end 10 | 11 | def self.find(place_name) 12 | url = "#{URL}q=#{ERB::Util.url_encode(place_name)}#{FIXED_PARAMETERS}" 13 | resp = Net::HTTP.get_response(URI.parse(url)) 14 | data = resp.body 15 | 16 | result = JSON.parse(data) 17 | location = nil 18 | location = result['geonames'].first if result && result['geonames'] 19 | 20 | location 21 | end 22 | 23 | def self.add_cmr_param(params, place_name) 24 | location = find(place_name) 25 | params[:point] = "#{location['lng']},#{location['lat']}" if location 26 | params 27 | end 28 | end -------------------------------------------------------------------------------- /features/granules/html/search_by_placename.feature: -------------------------------------------------------------------------------- 1 | @granules_search_by_placename_html 2 | Feature: Retrieve granules in html format 3 | In order to obtain granule products 4 | as an open search user 5 | I should be able to specify a place name as spatial search constraint 6 | 7 | Scenario: Search for granule using placename 8 | Given I have executed a html granule search with the following parameters: 9 | | input | value | 10 | | spatial_type | Place Name | 11 | | placeName | Bowness-on-Solway | 12 | | shortName | SampleShortName | 13 | Then I should see 1 granule result 14 | And granule result 1 should have a the following echo characteristics, 15 | | characteristic | value | 16 | | granule_ur | BownessOnSolwayGranule | 17 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /spec/controllers/health_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe HealthController do 4 | 5 | describe "GET 'index'" do 6 | it 'returns good status' do 7 | VCR.use_cassette 'models/health/cmr_good', :decode_compressed_response => true, :record => :once do 8 | get 'index', :format => :json 9 | expect(response.status).to eq(200) 10 | expect(response.body).to eq('{"cmr-search":{"ok?":true}}') 11 | end 12 | end 13 | 14 | it 'returns bad status' do 15 | VCR.use_cassette 'models/health/cmr_bad', :decode_compressed_response => true, :record => :once do 16 | get 'index', :format => :json 17 | expect(response.status).to eq(503) 18 | expect(response.body).to eq('{"cmr-search":{"ok?":false}}') 19 | end 20 | end 21 | 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /config/initializers/rufus_scheduled_tasks.rb: -------------------------------------------------------------------------------- 1 | # This initializer will run all the CMR OpenSearch scheduled tasks 2 | # If various durations are needed for various scheduler tasks, different scheduler runs should be used for individual 3 | # tasks 4 | require 'rufus-scheduler' 5 | 6 | s = Rufus::Scheduler.singleton 7 | # recurrent midnight run, no overlap of tasks, tasks executed in their own threads not in the scheduler thread 8 | s.cron'00 00 * * *', :overlap => false, :timeout => '10m' do 9 | begin 10 | eosdis_tagger = EosdisTagger.new 11 | # decided with single post instead of the many posts (one per provider) 12 | eosdis_tagger.execute_scheduled_task_single_post 13 | rescue Rufus::Scheduler::TimeoutError 14 | Rails.logger.info "10m TIMEOUT occured for run of scheduled EOSDIS tagging task: #{Time.now}" 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended to check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(:version => 0) do 15 | 16 | end 17 | -------------------------------------------------------------------------------- /features/collections/atom/search_to_geoss_dataset.feature: -------------------------------------------------------------------------------- 1 | @datasets_search_geoss_atom 2 | Feature: Retrieve collections in atom format with geoss element 3 | In order to obtain collection products 4 | as an open search user 5 | I should be able to search for geoss collections using a machine-readable format 6 | 7 | Scenario: Search for collection using keyword 8 | Given I have executed a collection search with the following parameters: 9 | | clientId | keyword | 10 | | foo | GEOS_TEST | 11 | Then I should see a valid collection atom response 12 | And I should see an open search query node in the results corresponding to: 13 | | os:searchTerms | 14 | | GEOS_TEST | 15 | Then I should see 2 results 16 | And result 1 should have a geoss tag 17 | And result 2 should not have a geoss tag 18 | 19 | -------------------------------------------------------------------------------- /features/collections/atom/search_to_collection_specific_granule_osdd_link.feature: -------------------------------------------------------------------------------- 1 | @datasets_search_collection_specific_granule_osdd 2 | Feature: Retrieve collections in atom format with collection specific granule osdd link 3 | In order to obtain collection products 4 | as an open search user 5 | I should be able to search for collections using a machine-readable format 6 | 7 | Scenario: Search for collection using keyword 8 | Given I have executed a collection search with the following parameters: 9 | | clientId | keyword | 10 | | foo | C1200382883-CMR_ONLY | 11 | Then I should see 1 result 12 | And result 1 should have a link to a granule open search descriptor document 13 | And result 1 should have a link href of 'http://osdd.org/data/amsre/someopensearchdescription.xml' for the granule open search descriptor document 14 | -------------------------------------------------------------------------------- /features/granules/atom/search_by_uid.feature: -------------------------------------------------------------------------------- 1 | @granules_search_by_uid_atom 2 | Feature: Retrieve granules in atom format 3 | In order to obtain granule products 4 | as an open search user 5 | I should be able to search for granules using a machine-readable format using a unique id associated with that granule 6 | 7 | Scenario: Search for granule using spatial bounding box 8 | Given I have executed a granule search with the following parameters: 9 | | clientId | boundingBox | 10 | | foo | -5,-5,5,5 | 11 | Then I should see a valid granule atom response 12 | And I should see 1 result 13 | And I execute a granule search using the unique id associated with result number 1 14 | And I should see 1 result 15 | And result 1 should have a the following echo characteristics, 16 | | producerGranuleId | 17 | | SpatialTestingGranule2 | 18 | 19 | -------------------------------------------------------------------------------- /features/collections/html/search_by_uid.feature: -------------------------------------------------------------------------------- 1 | @datasets_search_by_uid_html 2 | Feature: Retrieve collections in html format 3 | In order to obtain collection products 4 | as an open search user 5 | I should be able to search for collections using a unique id with a human-centric mechanism 6 | 7 | Scenario: Search for collection using uid 8 | Given I have executed a html collection search with the following parameters: 9 | | input | value | 10 | | keyword | First dataset for open search | 11 | Then I should see 1 collection result 12 | And I execute a html collection search using the unique id associated with result number 1 13 | Then I should see 1 collection result 14 | And collection result 1 should have a the following echo characteristics, 15 | | characteristic | value | 16 | | short_name | FirstDataset | 17 | 18 | -------------------------------------------------------------------------------- /features/collections/atom/search_by_cwic_only.feature: -------------------------------------------------------------------------------- 1 | @datasets_search_by_cwic_only_atom 2 | Feature: Retrieve collections in atom format that are in CWIC 3 | In order to obtain collection products 4 | as an open search user 5 | I should be able to search for collections using a machine-readable format that are resident in CWIC 6 | 7 | Scenario: Search for collection from CWIC 8 | Given I have executed a collection search with the following parameters: 9 | | clientId | isCwic | 10 | | foo | true | 11 | Then I should see a valid collection atom response 12 | And I should see 2 results 13 | 14 | Scenario: Search for collection 15 | Given I have executed a collection search with the following parameters: 16 | | clientId | isCwic | 17 | | foo | | 18 | Then I should see a valid collection atom response 19 | And I should see 3 results 20 | 21 | -------------------------------------------------------------------------------- /features/collections/atom/search_by_geoss_only.feature: -------------------------------------------------------------------------------- 1 | @datasets_search_by_geoss_only_atom 2 | Feature: Retrieve collections in atom format that are GEOSS collections 3 | In order to obtain collection products 4 | as an open search user 5 | I should be able to search for collections using a machine-readable format that are GEOSS collections 6 | 7 | Scenario: Search for collection from GEOSS 8 | Given I have executed a collection search with the following parameters: 9 | | clientId | isGeoss | 10 | | foo | true | 11 | Then I should see a valid collection atom response 12 | And I should see 2 results 13 | 14 | Scenario: Search for collection 15 | Given I have executed a collection search with the following parameters: 16 | | clientId | isGeoss | 17 | | foo | | 18 | Then I should see a valid collection atom response 19 | And I should see 3 results 20 | 21 | -------------------------------------------------------------------------------- /app/services/well_formed_text.rb: -------------------------------------------------------------------------------- 1 | require 'rgeo' 2 | 3 | class WellFormedText 4 | def self.add_cmr_param (params, well_known_text) 5 | factory = RGeo::Cartesian.factory 6 | 7 | geometry = factory.parse_wkt(well_known_text) 8 | 9 | params[:point] = "#{geometry.x},#{geometry.y}" if ::RGeo::Feature::Point.check_type(geometry) 10 | if ::RGeo::Feature::LineString.check_type(geometry) 11 | params[:line] = '' 12 | geometry.points.each do |point| 13 | params[:line] += "#{point.x},#{point.y}," 14 | end 15 | params[:line].chop! 16 | end 17 | if ::RGeo::Feature::Polygon.check_type(geometry) 18 | params[:polygon] = '' 19 | geometry.exterior_ring.points.reverse_each do |point| 20 | params[:polygon] += "#{point.x},#{point.y}," 21 | end 22 | params[:polygon].chop! 23 | end 24 | params 25 | end 26 | 27 | def self.to_wkt(param) 28 | 29 | end 30 | end -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile ~/.gitignore_global 6 | 7 | # Ignore bundler config 8 | .bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | 13 | # Ignore all logfiles and tempfiles. 14 | /log/*.log 15 | /tmp 16 | 17 | /bin 18 | /vendor/bundle 19 | /vendor/cache 20 | .idea 21 | config/application.yml 22 | fixtures.cache 23 | 24 | public/assets 25 | public/opensearch 26 | 27 | rails_best_practices_output.html 28 | test/reports 29 | 30 | config/errbit.yml 31 | 32 | brakeman.html 33 | 34 | rails_best_practices.html 35 | 36 | config/secret_token.yml 37 | 38 | holepicker.txt 39 | 40 | *.code-workspace 41 | flipper.pstore 42 | 43 | /coverage 44 | /notes 45 | -------------------------------------------------------------------------------- /features/granules/html/search_by_uid.feature: -------------------------------------------------------------------------------- 1 | @granules_search_by_uid_html 2 | Feature: Retrieve granules in atom format 3 | In order to obtain granule products 4 | as an open search user 5 | I should be able to search for granules using a machine-readable format using a unique id associated with that granule 6 | 7 | Scenario: Search for granule using spatial bounding box 8 | Given I have executed a html granule search with the following parameters: 9 | | input | value | 10 | | boundingBox | -5,-5,5,5 | 11 | | shortName | SampleShortName | 12 | Then I should see 1 granule result 13 | And I execute a html granule search using the unique id associated with result number 1 14 | Then I should see 1 granule result 15 | And granule result 1 should have a the following echo characteristics, 16 | | characteristic | value | 17 | | granule_ur | SpatialTestingGranule2 | 18 | 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Ruby image 2 | FROM ruby:3.1.4 3 | 4 | ENV DEBIAN_FRONTEND noninteractive 5 | 6 | # Install dependencies 7 | RUN apt-get update -qq && apt-get install -y apt-utils debian-archive-keyring 8 | RUN apt-get install -y build-essential libpq-dev 9 | 10 | RUN mkdir /cmr-opensearch 11 | WORKDIR /cmr-opensearch 12 | 13 | # Copy ruby version file 14 | COPY .ruby-version /cmr-opensearch/.ruby-version 15 | 16 | # Copy Gemfiles 17 | COPY Gemfile /cmr-opensearch/Gemfile 18 | COPY Gemfile.lock /cmr-opensearch/Gemfile.lock 19 | 20 | #Always bundle before copying app src. 21 | # Prevent bundler warnings; 22 | # ensure that the bundler version executed is >= that which created Gemfile.lock 23 | 24 | RUN gem install bundler 25 | 26 | # Finish establishing our Ruby enviornment 27 | RUN bundle config --global silence_root_warning 1 28 | RUN bundle install 29 | 30 | # Copy the Rails application into place 31 | COPY . /cmr-opensearch 32 | 33 | RUN bundle exec rake assets:precompile -------------------------------------------------------------------------------- /spec/models/health_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Health do 4 | describe 'health' do 5 | it 'is possible to report a good status from CMR search' do 6 | VCR.use_cassette 'models/health/cmr_good', :decode_compressed_response => true, :record => :once do 7 | h = Health.new 8 | expect(h.ok?).to eq(true) 9 | end 10 | end 11 | 12 | it 'is possible to report a bad status from CMR search due to a bad element' do 13 | VCR.use_cassette 'models/health/cmr_bad', :decode_compressed_response => true, :record => :once do 14 | h = Health.new 15 | expect(h.ok?).to eq(false) 16 | end 17 | end 18 | 19 | it 'is possible to report a good status from CMR search due to a bad status' do 20 | VCR.use_cassette 'models/health/cmr_bad_status', :decode_compressed_response => true, :record => :once do 21 | h = Health.new 22 | expect(h.ok?).to eq(false) 23 | end 24 | end 25 | end 26 | end -------------------------------------------------------------------------------- /app/services/connection_verifier.rb: -------------------------------------------------------------------------------- 1 | 2 | # This was created for NCRs 11012624 and 11012632. We found a concurrency bug in Rails' ActiveRecord ConnectionPool class. The problem 3 | # seems to be causing connections to be shared across threads sporadically. We added this module as a stop gap to help verify that we have not 4 | # received a bad or in use connection. The verify method fails if a connection is in a transaction that already started. 5 | module ConnectionVerifier 6 | 7 | MESSAGE = "Detected connection with existing transaction open" 8 | 9 | def self.verify_not_in_transaction 10 | conn = ActiveRecord::Base.connection 11 | raw_conn = conn.raw_connection 12 | if raw_conn.auto_commit == false || conn.open_transactions != 0 13 | message = "#{MESSAGE} auto_commit is #{raw_conn.auto_commit}, open transactions is #{conn.open_transactions}" 14 | Rails.logger.error(message) 15 | raise RestErrors::ServiceUnavailable.new(message) 16 | end 17 | end 18 | 19 | 20 | end -------------------------------------------------------------------------------- /spec/services/place_name_to_geometry_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe PlaceNameToPoint do 4 | describe 'exists' do 5 | it 'should validate an existing place name' do 6 | VCR.use_cassette 'services/place_name_to_geometry', :record => :once do 7 | expect(PlaceNameToPoint.exists?("Bowness-on-solway")).to_not equal(nil) 8 | end 9 | 10 | end 11 | it 'should not validate an existing place name' do 12 | VCR.use_cassette 'services/place_name_to_geometry', :record => :once do 13 | expect(PlaceNameToPoint.exists?('dougopolis')).to equal(nil) 14 | end 15 | end 16 | end 17 | describe 'add_cmr_param' do 18 | it 'should not convert a bogus place name' do 19 | VCR.use_cassette 'services/place_name_to_geometry', :record => :once do 20 | echo_params = {} 21 | echo_params = PlaceNameToPoint.add_cmr_param(echo_params, 'dougopolis') 22 | expect(echo_params[:point]).to equal(nil) 23 | end 24 | end 25 | end 26 | end -------------------------------------------------------------------------------- /app/assets/javascripts/results.js: -------------------------------------------------------------------------------- 1 | // Place all the behaviors and hooks related to the matching controller here. 2 | // All this logic will automatically be available in application.js. 3 | 4 | $(document).ready(function () { 5 | // Attach calendar widgets 6 | var temporalOptions = { 7 | timezoneIso8601:true, 8 | separator:'T', 9 | timeSuffix:'Z', 10 | showSecond:true, 11 | dateFormat:'yy-mm-dd', 12 | timeFormat:'hh:mm:ss' 13 | }; 14 | 15 | $('#startTime').datetimepicker(temporalOptions); 16 | $('#endTime').datetimepicker(temporalOptions); 17 | 18 | // Show relevant spatial extent 19 | $('.bbox, .geometry, .placename').hide(); 20 | var spatial_type = $('#spatial_type').val(); 21 | $('.' + spatial_type).show(); 22 | 23 | $('#spatial_type').change(function () { 24 | $('.bbox, .geometry, .placename').hide(); 25 | var spatial_type = $('#spatial_type').val(); 26 | $('.' + spatial_type).show(); 27 | }); 28 | }); -------------------------------------------------------------------------------- /features/granules/atom/search_by_dataset_id.feature: -------------------------------------------------------------------------------- 1 | @granules_search_by_dataset_id_atom 2 | Feature: Retrieve granules in atom format 3 | In order to obtain granule products 4 | as an open search user 5 | I should be able to specify dataset id as spatial search constraint 6 | 7 | Scenario: Search for granule using dataset id 8 | Given I have executed a granule search with the following parameters: 9 | | clientId | datasetId | 10 | | foo | Cool Dataset | 11 | Then I should see a valid granule atom response 12 | And I should see 1 result 13 | And result 1 should have a the following echo characteristics, 14 | | datasetId | cool_dataset_id | 15 | 16 | Scenario: Search for granule using invalid dataset id 17 | Given I have executed a granule search with the following parameters: 18 | | clientId | datasetId | 19 | | foo | Uncool Dataset | 20 | Then I should see a valid granule atom response 21 | And I should see 0 results 22 | 23 | -------------------------------------------------------------------------------- /spec/views/collections/ceos_collections_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'CEOS collections searches', :type => :controller do 4 | include Rack::Test::Methods 5 | 6 | def app 7 | Rails.application 8 | end 9 | 10 | it 'returns CEOS collections in the result set when isCeos is true' do 11 | VCR.use_cassette 'views/collection/ceos_collections', :decode_compressed_response => true , :record => :once do 12 | get '/collections.atom?isCeos=true' 13 | expect(last_response.ok?).to be true 14 | feed = Nokogiri::XML(last_response.body) 15 | # Do we have 10 entries 16 | entries = feed.xpath('os:feed/os:entry', 'os' => 'http://www.w3.org/2005/Atom') 17 | expect(entries.size).to eq(10) 18 | # response entries are all CEOS collections 19 | entries.each_with_index do |entry, index| 20 | entry_is_ceos = entry.at_xpath('echo:is_ceos', 'echo' => 'https://cmr.earthdata.nasa.gov/search/site/docs/search/api.html#atom').text 21 | expect(entry_is_ceos).to eq('true') 22 | end 23 | end 24 | end 25 | 26 | end 27 | -------------------------------------------------------------------------------- /features/granules/atom/search_by_placename.feature: -------------------------------------------------------------------------------- 1 | @granules_search_by_placename_atom 2 | Feature: Retrieve granules in atom format 3 | In order to obtain granule products 4 | as an open search user 5 | I should be able to specify a place name as spatial search constraint 6 | 7 | Scenario: Search for granule using placename 8 | Given I have executed a granule search with the following parameters: 9 | | clientId | placeName | 10 | | foo | Bowness-on-Solway | 11 | Then I should see a valid granule atom response 12 | And I should see 1 result 13 | And result 1 should have a the following echo characteristics, 14 | | producerGranuleId | 15 | | BownessOnSolwayGranule | 16 | 17 | Scenario: Search for granule using invalid placename 18 | Given I have executed a granule search with the following parameters: 19 | | clientId | placeName | 20 | | foo | dougopolis | 21 | Then I should see a valid error response 22 | And I should see 1 error 23 | And I should see the error "Placename dougopolis cannot be located" -------------------------------------------------------------------------------- /features/collections/atom/search_by_placename.feature: -------------------------------------------------------------------------------- 1 | @datasets_search_by_placename_atom 2 | Feature: Retrieve collections in atom format 3 | In order to obtain granule products 4 | as an open search user 5 | I should be able to specify a place name as spatial search constraint 6 | 7 | Scenario: Search for collection using placename 8 | Given I have executed a collection search with the following parameters: 9 | | clientId | placeName | 10 | | foo | Bowness-on-Solway | 11 | Then I should see a valid collection atom response 12 | And I should see 1 result 13 | And result 1 should have a the following echo characteristics, 14 | | shortName | 15 | | BownessOnSolwayDataset | 16 | 17 | Scenario: Search for collection using invalid placename 18 | Given I have executed a collection search with the following parameters: 19 | | clientId | placeName | 20 | | foo | dougopolis | 21 | Then I should see a valid error response 22 | And I should see 1 error 23 | And I should see the error "Placename dougopolis cannot be located" -------------------------------------------------------------------------------- /spec/views/granules/parent_identifier_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'granule search by parent concept-id' do 4 | include Rack::Test::Methods 5 | 6 | def app 7 | Rails.application 8 | end 9 | 10 | it 'retrieves all granules for the MOD-2QKM.005 collection when using its CMR concept_id value as the CMR OpenSearch parentIdentifier query parameter value' do 11 | VCR.use_cassette 'views/granule/parentIdentifier', :record => :once, :decode_compressed_response => true do 12 | # concept_id for MOD02QKM version 5 is C90758174-LAADS at https://cmr.earthdata.nasa.gov/search/concepts/C90758174-LAADS.xml 13 | # 971348 hits 14 | granule_opensearch_response = get '/granules.atom?parentIdentifier=C90758174-LAADS&clientId=openSearchSpecTest' 15 | assert granule_opensearch_response.ok? 16 | feed = Nokogiri::XML(granule_opensearch_response.body) 17 | total_results = feed.at_xpath('//atom:feed/os:totalResults', 'atom' => 'http://www.w3.org/2005/Atom', 'os' => 'http://a9.com/-/spec/opensearch/1.1/').text 18 | assert_equal '971348', total_results 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /spec/fixtures/models/health/cmr_bad.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://cmr.earthdata.nasa.gov/search/health 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - "*/*; q=0.5, application/xml" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Ruby 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Date: 22 | - Tue, 01 Mar 2016 13:50:48 GMT 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Cmr-Request-Id: 26 | - f97c7ea0-126a-44cd-8ff2-92e1e109da2a 27 | Content-Length: 28 | - '208' 29 | Server: 30 | - Jetty(9.2.z-SNAPSHOT) 31 | body: 32 | encoding: UTF-8 33 | string: '{"echo":{"ok?":true},"internal-metadata-db":{"ok?":false,"dependencies":{"oracle":{"ok?":true},"echo":{"ok?":true}}},"index-set":{"ok?":true,"dependencies":{"elastic_search":{"ok?":true},"echo":{"ok?":true}}}}' 34 | http_version: 35 | recorded_at: Tue, 01 Mar 2016 13:50:48 GMT 36 | recorded_with: VCR 2.9.2 37 | -------------------------------------------------------------------------------- /spec/fixtures/models/health/cmr_good.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://cmr.earthdata.nasa.gov/search/health 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - "*/*; q=0.5, application/xml" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Ruby 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Date: 22 | - Tue, 01 Mar 2016 13:50:47 GMT 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Cmr-Request-Id: 26 | - 69f9907c-696b-46c8-b9d5-d95106257f71 27 | Content-Length: 28 | - '208' 29 | Server: 30 | - Jetty(9.2.z-SNAPSHOT) 31 | body: 32 | encoding: UTF-8 33 | string: '{"echo":{"ok?":true},"internal-metadata-db":{"ok?":true,"dependencies":{"oracle":{"ok?":true},"echo":{"ok?":true}}},"index-set":{"ok?":true,"dependencies":{"elastic_search":{"ok?":true},"echo":{"ok?":true}}}}' 34 | http_version: 35 | recorded_at: Tue, 01 Mar 2016 13:50:47 GMT 36 | recorded_with: VCR 2.9.2 37 | -------------------------------------------------------------------------------- /spec/fixtures/models/health/cmr_bad_status.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://cmr.earthdata.nasa.gov/search/health 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - "*/*; q=0.5, application/xml" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Ruby 16 | response: 17 | status: 18 | code: 404 19 | message: Not Found 20 | headers: 21 | Date: 22 | - Tue, 01 Mar 2016 14:25:46 GMT 23 | Content-Type: 24 | - application/json; charset=utf-8 25 | Cmr-Request-Id: 26 | - d8081ee7-d76f-4dcc-acf8-2ab208ceeee2 27 | Content-Length: 28 | - '208' 29 | Server: 30 | - Jetty(9.2.z-SNAPSHOT) 31 | body: 32 | encoding: UTF-8 33 | string: '{"echo":{"ok?":true},"internal-metadata-db":{"ok?":true,"dependencies":{"oracle":{"ok?":true},"echo":{"ok?":true}}},"index-set":{"ok?":true,"dependencies":{"elastic_search":{"ok?":true},"echo":{"ok?":true}}}}' 34 | http_version: 35 | recorded_at: Tue, 01 Mar 2016 14:25:46 GMT 36 | recorded_with: VCR 2.9.2 37 | -------------------------------------------------------------------------------- /config/application.yml.template: -------------------------------------------------------------------------------- 1 | current: ¤t 2 | opensearch_url: http://localhost:3000/opensearch 3 | catalog_rest_endpoint: https://cmr.earthdata.nasa.gov/search/ 4 | USE_CWIC_SERVER: true 5 | echo_rest_endpoint: https://api.echo.nasa.gov/echo-rest/ 6 | edl_endpoint: https://urs.earthdata.nasa.gov/api/users/ 7 | contact: @MAIL_SENDER@ 8 | mode: @MODE@ 9 | public_catalog_rest_endpoint: https://cmr.earthdata.nasa.gov/search/ 10 | version: @ECHO_VERSION@ 11 | release_page: https://wiki.earthdata.nasa.gov/display/echo/Open+Search+API+release+information 12 | documentation_page: https://wiki.earthdata.nasa.gov/display/CMR/Common+Metadata+Repository+Home 13 | display_banner: '@DISPLAY_BANNER@' 14 | organization: NASA EOSDIS 15 | organization_contact_email: douglas.j.newman@nasa.gov 16 | organization_contact_name: Doug Newman 17 | 18 | development: 19 | <<: *current 20 | 21 | production: 22 | <<: *current 23 | catalog_rest_endpoint: @CMR_SEARCH_ENDPOINT@/ 24 | echo_rest_endpoint: @REST_ROOT_URL_FOR_REVERB@ 25 | opensearch_url: @PUBLIC_SSL_PROTOCOL@://@PUBLIC_SERVER@:@PUBLIC_SSL_PORT@/opensearch 26 | public_catalog_rest_endpoint: @CMR_PUBLIC_SEARCH_ENDPOINT@/ 27 | test: &test 28 | <<: *current 29 | -------------------------------------------------------------------------------- /app/assets/javascripts/granules.js: -------------------------------------------------------------------------------- 1 | /// Place all the behaviors and hooks related to the matching controller here. 2 | // All this logic will automatically be available in application.js. 3 | //= require jquery-1.7.2.min 4 | //= require jquery-ui-1.8.20.custom.min 5 | //= require jquery-ui-sliderAccess 6 | //= require jquery-ui-timepicker-addon 7 | //= require sweetalert.min 8 | //= require results 9 | 10 | $(function() { 11 | $('#granules-search-form').on("submit", function (e) { 12 | collectionConceptId = $('#parentIdentifier').val(); 13 | shortName = $('#shortName').val(); 14 | uniqueId = $('#uid').val(); 15 | // search must have at least one of the collection concept id, collection short name of granule unique id 16 | if (collectionConceptId === '' && shortName === '' && uniqueId === '') { 17 | swal({ 18 | title: "Granules search error!", 19 | text: "Granule searches require at least the Collection Concept ID or Short Name or Unique ID fields.", 20 | icon: "error" 21 | }); 22 | // consume the submit 23 | return false; 24 | } 25 | else { 26 | return true; 27 | } 28 | 29 | }); 30 | }); -------------------------------------------------------------------------------- /features/collections/atom/search_by_granules_only.feature: -------------------------------------------------------------------------------- 1 | @datasets_search_by_granules_only_atom 2 | Feature: Retrieve collections in atom format that have granules 3 | In order to obtain collection products 4 | as an open search user 5 | I should be able to search for collections using a machine-readable format that have granules 6 | 7 | Scenario: Search for collection with granules 8 | Given I have executed a collection search with the following parameters: 9 | | clientId | hasGranules | 10 | | foo | true | 11 | Then I should see a valid collection atom response 12 | And I should see 2 results 13 | 14 | Scenario: Search for collection without granules 15 | Given I have executed a collection search with the following parameters: 16 | | clientId | hasGranules | 17 | | foo | false | 18 | Then I should see a valid collection atom response 19 | And I should see 1 result 20 | 21 | Scenario: Search for collection without either 22 | Given I have executed a collection search with the following parameters: 23 | | clientId | hasGranules | 24 | | foo | | 25 | Then I should see a valid collection atom response 26 | And I should see 3 results 27 | 28 | -------------------------------------------------------------------------------- /spec/views/collections/provider_search_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'provider search behavior' do 4 | include Rack::Test::Methods 5 | 6 | def app 7 | Rails.application 8 | end 9 | 10 | # Not quite clear if provider and data_center are the same. Some confusing explanations below: 11 | # archive center is a data center with a role of ARCHIVER 12 | # data centers can be one or more of ARCHIVER, ORIGINATOR, PROCESSOR and DISTRIBUTOR 13 | # Most likely, a provider is a data center with a role of ORIGINATOR, it is the data owner 14 | it 'returns the correct provider in the results for a provider search' do 15 | VCR.use_cassette 'views/collection/cmr_larc_collections', :decode_compressed_response => true, :record => :once do 16 | get '/datasets.atom?provider=larc&clientId=larc1', nil, {'Cwic-User' => 'test'} 17 | expect(last_response.ok?).to be true 18 | feed = Nokogiri::XML(last_response.body) 19 | results = feed.xpath('os:feed/os:entry/echo:dataCenter', 'os' => 'http://www.w3.org/2005/Atom', 'echo' => 'https://cmr.earthdata.nasa.gov/search/site/docs/search/api.html#atom') 20 | results.each do |result| 21 | expect(result.text).to eq 'LARC' 22 | end 23 | end 24 | end 25 | end 26 | 27 | -------------------------------------------------------------------------------- /spec/views/granules/data_links_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'granule data links disambiguation' do 4 | include Rack::Test::Methods 5 | 6 | def app 7 | Rails.application 8 | end 9 | 10 | # the CMR result contains two data links per granule, after processing it we are left with a single data link per granule 11 | it 'there is a single granule data access link in a correctly processed CMD granule OpenSearch result' do 12 | VCR.use_cassette 'views/granule/datalinks', :record => :once do 13 | granule_opensearch_response = get '/granules.atom?datasetId=&shortName=MODISA_L3m_CHL&versionId=2014&dataCenter=OB_DAAC&boundingBox=&geometry=&placeName=&startTime=2005-01-01T00%3A00%3A01Z&endTime=2005-01-01T23%3A59%3A59Z&cursor=1&numberOfResults=1&uid=&clientId=openSearchSpecTest' 14 | assert granule_opensearch_response.ok? 15 | feed = Nokogiri::XML(granule_opensearch_response.body) 16 | feed_entries = feed.xpath('//atom:feed/atom:entry', 'atom' => 'http://www.w3.org/2005/Atom') 17 | feed_entries.each do |entry| 18 | datalinks_count = entry.xpath('atom:link[@rel="enclosure"]', 'atom' => 'http://www.w3.org/2005/Atom') 19 | assert_equal 1, datalinks_count.size 20 | end 21 | end 22 | end 23 | end -------------------------------------------------------------------------------- /features/collections/atom/search_by_uid.feature: -------------------------------------------------------------------------------- 1 | @datasets_search_by_uid_atom 2 | Feature: Retrieve collections in atom format 3 | In order to obtain collection products 4 | as an open search user 5 | I should be able to search for collections using a machine-readable format using a unique id associated with that dataset 6 | 7 | Scenario: Search for collection using unique id 8 | Given I have executed a collection search with the following parameters: 9 | | clientId | keyword | 10 | | foo | First dataset for open search | 11 | Then I should see a valid collection atom response 12 | And I execute a collection search using the unique id associated with result number 1 13 | Then I should see a valid collection atom response 14 | Then I should see 1 result 15 | And result 1 should have a the following echo characteristics, 16 | | shortName | versionId | datasetId | dataCenter | 17 | | FirstDataset | 1 | First dataset for open search | OS_PROV_1 | 18 | And result 1 should have a description of "This is a description" 19 | And result 1 should not have a link to a granule search 20 | And result 1 should not have a link to a granule open search descriptor document 21 | 22 | -------------------------------------------------------------------------------- /app/controllers/holdings_controller.rb: -------------------------------------------------------------------------------- 1 | class HoldingsController < ApplicationController 2 | respond_to :json 3 | 4 | def index 5 | @holdings = {} 6 | 7 | Rails.configuration.holdings_providers.each do |query_config| 8 | provider = query_config['provider'] 9 | 10 | holding = Holding.find(provider) 11 | 12 | @holdings[provider.downcase] = { 13 | 'collections' => holding.fetch('count', 0), 14 | 'granules' => holding.fetch('items', {}).map { |key, value| value.fetch('count', 0) }.sum, 15 | 'last_error' => holding['last_error'], 16 | 'last_requested_at' => holding['last_requested_at'], 17 | 'updated_at' => holding['updated_at'] 18 | } 19 | end 20 | 21 | respond_to do |format| 22 | format.json do 23 | render :json => @holdings, :status => :ok 24 | end 25 | end 26 | end 27 | 28 | def show 29 | @holdings = Holding.find(params[:id]) 30 | 31 | respond_to do |format| 32 | format.json do 33 | if @holdings.blank? 34 | render :json => "{\"error\":\"Provider `#{params[:id]}` not found\"}", status: :not_found 35 | else 36 | render :json => @holdings, :status => :ok 37 | end 38 | end 39 | end 40 | end 41 | end -------------------------------------------------------------------------------- /app/assets/stylesheets/scaffolds.css.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #fff; 3 | color: #333; 4 | font-family: verdana, arial, helvetica, sans-serif; 5 | font-size: 13px; 6 | line-height: 18px; 7 | } 8 | 9 | p, ol, ul, td { 10 | font-family: verdana, arial, helvetica, sans-serif; 11 | font-size: 13px; 12 | line-height: 18px; 13 | } 14 | 15 | pre { 16 | background-color: #eee; 17 | padding: 10px; 18 | font-size: 11px; 19 | } 20 | 21 | a { 22 | color: #000; 23 | &:visited { 24 | color: #666; 25 | } 26 | &:hover { 27 | /*color: #fff; */ 28 | background-color: #000; 29 | } 30 | } 31 | 32 | div { 33 | &.field, &.actions { 34 | margin-bottom: 10px; 35 | } 36 | } 37 | 38 | #notice { 39 | color: green; 40 | } 41 | 42 | .field_with_errors { 43 | padding: 2px; 44 | background-color: red; 45 | display: table; 46 | } 47 | 48 | #error_explanation { 49 | width: 450px; 50 | border: 2px solid red; 51 | padding: 7px; 52 | padding-bottom: 0; 53 | margin-bottom: 20px; 54 | background-color: #f0f0f0; 55 | h2 { 56 | text-align: left; 57 | font-weight: bold; 58 | padding: 5px 5px 5px 15px; 59 | font-size: 12px; 60 | margin: -7px; 61 | margin-bottom: 0px; 62 | background-color: #c00; 63 | color: #fff; 64 | } 65 | ul li { 66 | font-size: 12px; 67 | list-style: square; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /features/collections/html/search_by_granules_only.feature: -------------------------------------------------------------------------------- 1 | @datasets_search_by_granules_only_html 2 | Feature: Retrieve collections in html format 3 | In order to obtain collection products 4 | as an open search user 5 | I should be able to search for collections using a unique id with a human-centric mechanism 6 | 7 | Scenario: Search for collections with granules 8 | 9 | Given I have executed a html collection search with the following parameters: 10 | | input | value | 11 | | hasGranules | Yes | 12 | Then I should see 2 collection results 13 | And I should see the following inputs: 14 | | input | value | 15 | | hasGranules | true | 16 | 17 | Scenario: Search for collections without granules 18 | Given I have executed a html collection search with the following parameters: 19 | | input | value | 20 | | hasGranules | No | 21 | And I should see 1 collection result 22 | And I should see the following inputs: 23 | | input | value | 24 | | hasGranules | false | 25 | 26 | Scenario: Search for collections without either 27 | Given I am on the open search home page 28 | And I search for collections 29 | Then I should see the collection search form 30 | And I should see 3 collection results 31 | And I should see the following inputs: 32 | | input | value | 33 | | hasGranules | | 34 | 35 | -------------------------------------------------------------------------------- /features/collections/html/search_by_cwic_only.feature: -------------------------------------------------------------------------------- 1 | @datasets_search_by_cwic_only_html 2 | Feature: Retrieve collections in html format 3 | In order to obtain collection products 4 | as an open search user 5 | I should be able to search for collections using a machine-readable format that are resident in CWIC 6 | 7 | Scenario: Search for collections from CWIC 8 | 9 | Given I have executed a html collection search with the following parameters: 10 | | input | value | 11 | | isCwic | Yes | 12 | Then I should see 2 collection results 13 | And I should see the following inputs: 14 | | input | value | 15 | | isCwic | true | 16 | 17 | Scenario: Search for collections 18 | Given I am on the open search home page 19 | And I search for collections 20 | Then I should see the collection search form 21 | And I should see 3 collection results 22 | And I should see the following inputs: 23 | | input | value | 24 | | isCwic | | 25 | 26 | Scenario: Search for collections and Don't Care 27 | Given I have executed a html collection search with the following parameters: 28 | | input | value | 29 | | isCwic | Don't care | 30 | Then I should see the collection search form 31 | And I should see 3 collection results 32 | And I should see the following inputs: 33 | | input | value | 34 | | isCwic | | 35 | 36 | 37 | -------------------------------------------------------------------------------- /features/collections/html/search_by_geoss_only.feature: -------------------------------------------------------------------------------- 1 | @datasets_search_by_geoss_only_html 2 | Feature: Retrieve collections in html format 3 | In order to obtain collection products 4 | as an open search user 5 | I should be able to search for collections using a machine-readable format that are GEOSS collections 6 | 7 | Scenario: Search for collections from GEOSS 8 | 9 | Given I have executed a html collection search with the following parameters: 10 | | input | value | 11 | | isGeoss | Yes | 12 | Then I should see 2 collection results 13 | And I should see the following inputs: 14 | | input | value | 15 | | isGeoss | true | 16 | 17 | Scenario: Search for collections 18 | Given I am on the open search home page 19 | And I search for collections 20 | Then I should see the collection search form 21 | And I should see 3 collection results 22 | And I should see the following inputs: 23 | | input | value | 24 | | isGeoss | | 25 | 26 | Scenario: Search for collections and Don't Care 27 | Given I have executed a html collection search with the following parameters: 28 | | input | value | 29 | | isCwic | Don't care | 30 | Then I should see the collection search form 31 | And I should see 3 collection results 32 | And I should see the following inputs: 33 | | input | value | 34 | | isGeoss | | 35 | 36 | 37 | -------------------------------------------------------------------------------- /spec/services/well_formed_text_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe WellFormedText do 4 | 5 | describe "point conversion" do 6 | it "should convert a well formed text of type point to an echo point parameter" do 7 | echo_params = {} 8 | echo_params = WellFormedText.add_cmr_param(echo_params, "POINT (1 2)") 9 | expect(echo_params[:point]).to eq("1.0,2.0") 10 | end 11 | end 12 | describe "line conversion" do 13 | it "should convert a well formed text of type line to an echo line parameter" do 14 | echo_params = {} 15 | echo_params = WellFormedText.add_cmr_param(echo_params, "LINESTRING (30 10, 10 30, 40 40)") 16 | expect(echo_params[:line]).to eq("30.0,10.0,10.0,30.0,40.0,40.0") 17 | end 18 | end 19 | describe "polygon conversion" do 20 | it "should convert a well formed text of type polygon to an echo polygon parameter" do 21 | echo_params = {} 22 | echo_params = WellFormedText.add_cmr_param(echo_params, "POLYGON ((30 10, 10 20, 20 40, 40 40, 30 10))") 23 | expect(echo_params[:polygon]).to eq("30.0,10.0,40.0,40.0,20.0,40.0,10.0,20.0,30.0,10.0") 24 | end 25 | it "should convert a well formed text of type polygon to an echo polygon parameter" do 26 | echo_params = {} 27 | echo_params = WellFormedText.add_cmr_param(echo_params, "POLYGON ((1 1, -1 2, 1 3, 1 1))") 28 | expect(echo_params[:polygon]).to eq("1.0,1.0,1.0,3.0,-1.0,2.0,1.0,1.0") 29 | end 30 | end 31 | end -------------------------------------------------------------------------------- /features/collections/atom/search_to_cwic_dataset.feature: -------------------------------------------------------------------------------- 1 | @datasets_search_cwic_atom 2 | Feature: Retrieve collections in atom format with CWIC links 3 | In order to obtain collection products 4 | as an open search user 5 | I should be able to search for collections using a machine-readable format 6 | 7 | Scenario: Search for collection using keyword 8 | Given I have executed a collection search with the following parameters: 9 | | clientId | keyword | 10 | | foo | GCMDTEST | 11 | Then I should see a valid collection atom response 12 | And I should see an open search query node in the results corresponding to: 13 | | os:searchTerms | 14 | | GCMDTEST | 15 | Then I should see 10 results 16 | And result 1 should not have a link to a granule search 17 | And result 1 should have a link to a granule open search descriptor document 18 | And result 1 should have a link href of 'http://localhost:3000/opensearch/granules/descriptor_document.xml?collectionConceptId=C1200018718-GCMDTEST&clientId=foo' for the granule open search descriptor document 19 | And result 2 should have a link to a granule open search descriptor document 20 | And result 2 should have a link href of 'http://localhost:3000/opensearch/granules/descriptor_document.xml?collectionConceptId=C1200018712-GCMDTEST&clientId=foo' for the granule open search descriptor document 21 | And result 3 should not have a link to a granule search 22 | And result 3 should not have a link to a granule open search descriptor document 23 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | File.read('.ruby-version').chomp.split('-')[1] { |f| "ruby '#{f}'"} 4 | 5 | gem 'activeresource', '>= 5.1.1' 6 | gem 'ffi', '1.15.5' 7 | gem 'flipper' 8 | gem 'georuby', '~> 2.5.2' 9 | gem 'jquery-rails', '~> 4.4.0' 10 | gem 'loofah', '>= 2.3.1' 11 | gem 'mimemagic', '~> 0.3.7' 12 | gem 'nokogiri', '>= 1.18.9' 13 | gem 'rack', '>= 2.2.14' 14 | gem 'rails-controller-testing' 15 | gem 'rails', '~> 7.1.5' 16 | gem 'responders', '~> 3.0' 17 | gem 'rest-client', '~> 2.0.2' 18 | gem 'rgeo', '~> 1.0.0' 19 | gem 'unicorn' 20 | gem 'webrick' 21 | 22 | # Gems used only for assets and not required 23 | # in production environments by default. 24 | group :assets do 25 | gem 'coffee-rails', '~> 4.2.2' 26 | gem 'execjs', '~> 2.7.0' 27 | gem 'sass-rails', '~> 5.0.7' 28 | gem 'sass', '~> 3.5.5' 29 | gem 'uglifier', '~> 4.1.6' 30 | end 31 | 32 | group :production, :sit, :uat do 33 | # to compensate for the CMR static tagging functionality 34 | gem 'rails_12factor' 35 | gem 'redis' 36 | gem 'rufus-scheduler' 37 | end 38 | 39 | group :development do 40 | gem 'pry-rails' 41 | # better error handling 42 | gem 'better_errors' 43 | gem 'binding_of_caller' 44 | gem 'byebug' 45 | gem 'rubocop' 46 | end 47 | 48 | group :test, :development do 49 | gem 'rspec_junit_formatter', '~> 0.3.0' 50 | gem 'rspec-activemodel-mocks', '~> 1.0.3' 51 | gem 'rspec-rails' 52 | end 53 | 54 | group :test do 55 | gem 'simplecov', :require => false 56 | gem 'cucumber-rails', :require => false 57 | gem 'vcr' 58 | gem 'webmock' 59 | gem 'capybara' 60 | gem 'rack-test' 61 | end -------------------------------------------------------------------------------- /features/home/step_definitions/home_steps.rb: -------------------------------------------------------------------------------- 1 | Given /^I am on the open search home page$/ do 2 | visit('/') 3 | end 4 | 5 | Then /^(?:|I )should see "([^\"]*)"?$/ do |content| 6 | expect(page.has_content?(content)).to be true 7 | end 8 | 9 | Then /^I should not see "([^"]*)"$/ do |content| 10 | expect(page.has_content?(content)).to be false 11 | end 12 | 13 | Then /^I should see the title "(.*?)"$/ do |title_text| 14 | page.should have_selector('h1', :text => title_text) 15 | end 16 | 17 | Then /^I should see the subtitle "(.*?)"$/ do |title_text| 18 | page.should have_selector('h2', :text => title_text) 19 | end 20 | 21 | Then /^I should see a page title reading "(.*?)"$/ do |title_text| 22 | #expect(first('title').native.text).to eq title_text 23 | page.has_title? title_text 24 | end 25 | 26 | And(/^I should see the current version of CMR/) do 27 | within('footer') do 28 | page.should have_link("Release: #{Rails.configuration.version.strip}", :href=>'https://wiki.earthdata.nasa.gov/display/echo/Open+Search+API+release+information') 29 | end 30 | end 31 | 32 | And(/^I should see information regarding CMR OpenSearch release documentation$/) do 33 | assert page.has_content?('Release documentation can be found here') 34 | page.should have_link('here', :href=>'https://wiki.earthdata.nasa.gov/display/echo/Open+Search+API+release+information') 35 | end 36 | 37 | And(/^I should see a link to the CMR OpenSearch release documentation$/) do 38 | within('footer') do 39 | page.should have_link("#{Rails.configuration.version.strip}", :href=>'https://wiki.earthdata.nasa.gov/display/echo/Open+Search+API+release+information') 40 | end 41 | end 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // the compiled file. 9 | // 10 | // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD 11 | // GO AFTER THE REQUIRES BELOW. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require jquery-1.7.2.min 16 | //= require jquery-ui-1.8.20.custom.min 17 | //= require jquery-ui-sliderAccess 18 | //= require jquery-ui-timepicker-addon 19 | 20 | $(document).ready(function () { 21 | // Attach calendar widgets 22 | var temporalOptions = { 23 | timezoneIso8601:true, 24 | separator:'T', 25 | timeSuffix:'Z', 26 | showSecond:true, 27 | dateFormat:'yy-mm-dd', 28 | timeFormat:'hh:mm:ss' 29 | }; 30 | 31 | $('#startTime').datetimepicker(temporalOptions); 32 | $('#endTime').datetimepicker(temporalOptions); 33 | 34 | // Show relevant spatial extent 35 | $('.bbox, .geometry, .placename').hide(); 36 | var spatial_type = $('#spatial_type').val(); 37 | $('.' + spatial_type).show(); 38 | 39 | $('#spatial_type').change(function () { 40 | $('.bbox, .geometry, .placename').hide(); 41 | var spatial_type = $('#spatial_type').val(); 42 | $('.' + spatial_type).show(); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /app/views/shared/_pagination.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/views/granules/ceos_bp_1.2_compliance_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'granules searches compliance with CEOS Best Practices version 1.2' do 4 | include Rack::Test::Methods 5 | 6 | def app 7 | Rails.application 8 | end 9 | 10 | it 'unsupported query parameters are dropped from the request per CEOS-BP-009B' do 11 | VCR.use_cassette 'views/granule/ceos_bp_009b', :decode_compressed_response => true, :record => :once do 12 | get '/granules.atom?shortName=AST_L1B&unsupported_query_parameter=testvalue' 13 | assert last_response.ok? 14 | feed = Nokogiri::XML(last_response.body) 15 | assert_equal '3172006', feed.xpath('atom:feed/os:totalResults', 'os' => 'http://a9.com/-/spec/opensearch/1.1/', 'atom' => 'http://www.w3.org/2005/Atom').first.text 16 | assert_equal '10', feed.xpath('atom:feed/os:itemsPerPage', 'os' => 'http://a9.com/-/spec/opensearch/1.1/', 'atom' => 'http://www.w3.org/2005/Atom').first.text 17 | assert_equal '1', feed.xpath('atom:feed/os:startIndex', 'os' => 'http://a9.com/-/spec/opensearch/1.1/', 'atom' => 'http://www.w3.org/2005/Atom').first.text 18 | request_node = feed.xpath('atom:feed/os:Query', 'os' => 'http://a9.com/-/spec/opensearch/1.1/', 'atom' => 'http://www.w3.org/2005/Atom').first 19 | expect(request_node.keys.include?('role')).to be true 20 | expect(request_node.keys.include?('shortName')).to be true 21 | expect(request_node.keys.include?('unsupported_query_parameter')).to be false 22 | expect(request_node.values.include?('request')).to be true 23 | expect(request_node.values.include?('AST_L1B')).to be true 24 | expect(request_node.values.include?('testvalue')).to be false 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | ENV["RAILS_ENV"] ||= 'test' 3 | require File.expand_path("../../config/environment", __FILE__) 4 | require 'rspec/rails' 5 | 6 | require 'simplecov' 7 | SimpleCov.start 'rails' do 8 | add_filter 'spec' 9 | end 10 | 11 | # Requires supporting ruby files with custom matchers and macros, etc, 12 | # in spec/support/ and its subdirectories. 13 | Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} 14 | 15 | RSpec.configure do |config| 16 | # ## Mock Framework 17 | # 18 | # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: 19 | # 20 | # config.mock_with :mocha 21 | # config.mock_with :flexmock 22 | # config.mock_with :rr 23 | 24 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 25 | #config.fixture_path = "#{::Rails.root}/spec/fixtures" 26 | 27 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 28 | # examples within a transaction, remove the following line or assign false 29 | # instead of true. 30 | #config.use_transactional_fixtures = true 31 | 32 | # If true, the base class of anonymous controllers will be inferred 33 | # automatically. This will be the default behavior in future versions of 34 | # rspec-rails. 35 | config.infer_base_class_for_anonymous_controllers = false 36 | 37 | config.infer_spec_type_from_file_location! 38 | 39 | # Run specs in random order to surface order dependencies. If you find an 40 | # order dependency and want to debug it, you can fix the order by providing 41 | # the seed, which is printed after each run. 42 | # --seed 1234 43 | # config.order = "random" 44 | 45 | end 46 | -------------------------------------------------------------------------------- /spec/views/collections/ceos_bp_1.2_compliance_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'collections searches compliance with CEOS Best Practices version 1.2' do 4 | include Rack::Test::Methods 5 | 6 | def app 7 | Rails.application 8 | end 9 | it 'unsupported query parameters are dropped from the request per CEOS-BP-009B' do 10 | VCR.use_cassette 'views/collection/ceos_bp_009b', :decode_compressed_response => true, :record => :once do 11 | get '/collections.atom?shortName=AST_L1B&spatial_type=bbox&unsupported_query_parameter=testvalue' 12 | assert last_response.ok? 13 | feed = Nokogiri::XML(last_response.body) 14 | assert_equal '33354', feed.xpath('atom:feed/os:totalResults', 'os' => 'http://a9.com/-/spec/opensearch/1.1/', 'atom' => 'http://www.w3.org/2005/Atom').first.text 15 | assert_equal '10', feed.xpath('atom:feed/os:itemsPerPage', 'os' => 'http://a9.com/-/spec/opensearch/1.1/', 'atom' => 'http://www.w3.org/2005/Atom').first.text 16 | assert_equal '1', feed.xpath('atom:feed/os:startIndex', 'os' => 'http://a9.com/-/spec/opensearch/1.1/', 'atom' => 'http://www.w3.org/2005/Atom').first.text 17 | request_node = feed.xpath('atom:feed/os:Query', 'os' => 'http://a9.com/-/spec/opensearch/1.1/', 'atom' => 'http://www.w3.org/2005/Atom').first 18 | expect(request_node.keys.include?('role')).to be true 19 | expect(request_node.keys.include?('shortName')).to be true 20 | expect(request_node.keys.include?('unsupported_query_parameter')).to be false 21 | expect(request_node.values.include?('request')).to be true 22 | expect(request_node.values.include?('AST_L1B')).to be true 23 | expect(request_node.values.include?('testvalue')).to be false 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | */ 13 | p.required-field-note { 14 | text-align: right; 15 | } 16 | 17 | span.required { 18 | font-family: Courier New, monospace; 19 | color: rgb(231,76,60); 20 | font-size: 12pt; 21 | } 22 | 23 | form { 24 | margin-top: 1.5em; 25 | margin-right: 0px; 26 | margin-bottom: 1.5em; 27 | margin-left: 0px; 28 | display: table; 29 | label { 30 | display: table-cell; 31 | } 32 | input, select { 33 | display: table-cell; 34 | margin-left: 1em; 35 | } 36 | } 37 | 38 | p.form-parameter { 39 | padding-top: 2px; 40 | padding-bottom: 2px; 41 | display: table-row; 42 | select { 43 | width: 56em; 44 | } 45 | } 46 | footer { 47 | background-color: rgb(44, 62, 80); 48 | } 49 | 50 | .masthead-logo { 51 | 52 | a:link { 53 | background-image: url(cmr-opensearch-logo-small.png); 54 | 55 | } 56 | 57 | a:hover { 58 | background-image: url(cmr-opensearch-logo-small-hover.png); 59 | } 60 | } 61 | 62 | section.hero { 63 | padding-top: 0em; 64 | height: 150px; 65 | color: #ffffff; 66 | text-align: center; 67 | background-color: rgb(44, 62, 80); 68 | a:link, a:visited 69 | { 70 | color: #ffffff; 71 | text-decoration-line: none; 72 | text-decoration-style: solid; 73 | } 74 | } 75 | 76 | span.emphasis { 77 | font-weight: bold; 78 | } -------------------------------------------------------------------------------- /features/support/vcr.rb: -------------------------------------------------------------------------------- 1 | VCR.configure do |c| 2 | # By default VCR will intercept all http calls. We don't want it to intercept echo-access calls, just catalog-rest and echo-rest. 3 | #c.ignore_hosts '127.0.0.1', 'localhost' 4 | # The directory where your cassettes will be saved 5 | c.cassette_library_dir = 'features/fixtures' 6 | c.hook_into :webmock 7 | 8 | c.ignore_hosts '127.0.0.1', 'localhost' 9 | c.default_cassette_options = {:record => :once, :decode_compressed_response => true} 10 | #c.default_cassette_options = {:record => :new_episodes, :decode_compressed_response => true} 11 | end 12 | 13 | VCR.cucumber_tags do |t| 14 | t.tags '@datasets_search_atom', '@datasets_search_by_placename_atom', '@datasets_search_by_uid_atom', '@datasets_search_by_wkt_atom', '@datasets_search_dc_temporal_atom', '@datasets_search_traversal_atom', '@datasets_search_validation_atom' 15 | t.tags '@datasets_search_html', '@datasets_search_by_placename_html', '@datasets_search_by_uid_html', '@datasets_search_traversal_html', '@datasets_search_validation_html' 16 | 17 | t.tags '@granules_search_atom', '@granules_search_by_dataset_id_atom', '@granules_search_by_placename_atom', '@granules_search_by_uid_atom', '@granules_search_by_wkt_atom', '@granules_search_dc_temporal_atom', '@granules_search_traversal_atom', '@granules_search_validation_atom' 18 | t.tags '@granules_search_html', '@granules_search_by_placename_html', '@granules_search_by_uid_html', '@granules_search_validation_html' 19 | 20 | t.tags '@datasets_osdd', '@granules_osdd' 21 | 22 | t.tags '@datasets_search_cwic_atom' 23 | 24 | t.tags '@datasets_search_collection_specific_granule_osdd' 25 | 26 | t.tags '@datasets_search_by_granules_only_atom', '@datasets_search_by_granules_only_html' 27 | 28 | t.tags '@datasets_search_by_cwic_only_atom', '@datasets_search_by_cwic_only_html' 29 | 30 | t.tags '@datasets_search_by_geoss_only_atom', '@datasets_search_by_geoss_only_html', '@datasets_search_geoss_atom' 31 | end 32 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | EchoOpensearch::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Print deprecation notices to the Rails logger. 17 | config.active_support.deprecation = :log 18 | 19 | # Debug mode disables concatenation and preprocessing of assets. 20 | # This option may cause significant delays in view rendering with a large 21 | # number of complex assets. 22 | config.assets.debug = true 23 | 24 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 25 | # yet still be able to expire them through the digest params. 26 | config.assets.digest = true 27 | 28 | # Adds additional error checking when serving assets at runtime. 29 | # Checks for improperly declared sprockets dependencies. 30 | # Raises helpful error messages. 31 | config.assets.raise_runtime_errors = true 32 | 33 | # Raises error for missing translations 34 | # config.action_view.raise_on_missing_translations = true 35 | 36 | config.application_url = 'http://localhost:3000' 37 | config.relative_url_root = '/opensearch' 38 | config.graphql_endpoint = ENV['GRAPHQL_ENDPOINT'] 39 | config.cache_store = :redis_cache_store, { url: "redis://#{ENV['REDIS_URL']}:#{ENV['REDIS_PORT']}" } 40 | 41 | # Logging 42 | Rails.logger = Logger.new("#{Rails.root}/log/#{Rails.env}.log") 43 | Rails.logger.formatter = Logger::Formatter.new 44 | 45 | puts 'This is a dev deployment' 46 | end 47 | -------------------------------------------------------------------------------- /features/opensearch_descriptor_documents/collections.feature: -------------------------------------------------------------------------------- 1 | @datasets_osdd 2 | Feature: Retrieve collection Open Search Descriptor Document (OSDD) 3 | In order to obtain dataset and granule products 4 | as an open search user 5 | I should be able to obtain an open search descriptor document for datasets 6 | 7 | Scenario: Generate collection open search descriptor document 8 | Given I am on the open search home page 9 | And I fill in the "collections" open search descriptor form with a client id of "foo" 10 | And I click on "Generate" within the "collections" form 11 | Then I should see a collection open search descriptor document for client id "foo" 12 | 13 | Scenario: Generate collection open search descriptor document using an invalid client id 14 | Given I am on the open search home page 15 | And I fill in the "collections" open search descriptor form with a client id of "foo bar" 16 | And I click on "Generate" within the "collections" form 17 | Then I should see the error message "Unable to process request : Clientid is invalid, it must be an alpha-numeric string" 18 | 19 | Scenario: Generate collection open search descriptor document and use it to do a dataset search 20 | Given I am on the open search home page 21 | And I fill in the "collections" open search descriptor form with a client id of "foo" 22 | And I click on "Generate" within the "collections" form 23 | And I perform a dataset search using the open search descriptor template 24 | Then I should see a valid collection atom response 25 | 26 | Scenario: Generate collection open search descriptor document and see attribution and syndication 27 | Given I am on the open search home page 28 | And I fill in the "collections" open search descriptor form with a client id of "foo" 29 | And I click on "Generate" within the "collections" form 30 | Then I should see a collection open search descriptor document for client id "foo" 31 | And I should see an attribution of "NASA CMR" 32 | And I should see a syndication right of "open" 33 | -------------------------------------------------------------------------------- /spec/fixtures/views/granule/navigation_95268_offset.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://cmr.earthdata.nasa.gov/search/granules.atom?instrument=CZCS&offset=95268&page_size=10 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - "*/*; q=0.5, application/xml" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | Client-Id: 15 | - cmr-open-search 16 | User-Agent: 17 | - Ruby 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Date: 24 | - Wed, 28 Sep 2016 19:21:02 GMT 25 | Content-Type: 26 | - application/atom+xml; charset=utf-8 27 | Access-Control-Expose-Headers: 28 | - CMR-Hits, CMR-Request-Id 29 | Access-Control-Allow-Origin: 30 | - "*" 31 | Cmr-Hits: 32 | - '95268' 33 | Cmr-Took: 34 | - '2934' 35 | Cmr-Request-Id: 36 | - 3bfb940a-f997-42c7-9356-ced080b50853 37 | Content-Length: 38 | - '662' 39 | Server: 40 | - Jetty(9.2.z-SNAPSHOT) 41 | body: 42 | encoding: UTF-8 43 | string: 2016-09-28T19:21:05.425Zhttps://cmr.earthdata.nasa.gov:443/search/granules.atom?page_size=10&offset=95268&instrument=CZCSECHO granule metadata 49 | http_version: 50 | recorded_at: Wed, 28 Sep 2016 19:21:05 GMT 51 | recorded_with: VCR 2.9.2 52 | -------------------------------------------------------------------------------- /spec/fixtures/views/granule/navigation_95269_offset.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://cmr.earthdata.nasa.gov/search/granules.atom?instrument=CZCS&offset=95269&page_size=10 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - "*/*; q=0.5, application/xml" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | Client-Id: 15 | - cmr-open-search 16 | User-Agent: 17 | - Ruby 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Date: 24 | - Wed, 28 Sep 2016 20:10:51 GMT 25 | Content-Type: 26 | - application/atom+xml; charset=utf-8 27 | Access-Control-Expose-Headers: 28 | - CMR-Hits, CMR-Request-Id 29 | Access-Control-Allow-Origin: 30 | - "*" 31 | Cmr-Hits: 32 | - '95268' 33 | Cmr-Took: 34 | - '3722' 35 | Cmr-Request-Id: 36 | - 690ef7a2-29aa-4458-88b9-2a5b1178630a 37 | Content-Length: 38 | - '662' 39 | Server: 40 | - Jetty(9.2.z-SNAPSHOT) 41 | body: 42 | encoding: UTF-8 43 | string: 2016-09-28T20:10:54.838Zhttps://cmr.earthdata.nasa.gov:443/search/granules.atom?page_size=10&offset=95269&instrument=CZCSECHO granule metadata 49 | http_version: 50 | recorded_at: Wed, 28 Sep 2016 20:10:54 GMT 51 | recorded_with: VCR 2.9.2 52 | -------------------------------------------------------------------------------- /features/fixtures/cucumber_tags/datasets_search_validation_atom_old.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api.echo.nasa.gov/echo-rest//tokens 6 | body: 7 | encoding: US-ASCII 8 | string: |- 9 | 10 | guest 11 | guest@echo.nasa.gov 12 | foo-opensearch-dev 13 | 0.0.0.0 14 | 15 | headers: 16 | Accept: 17 | - '*/*; q=0.5, application/xml' 18 | Accept-Encoding: 19 | - gzip, deflate 20 | Content-Type: 21 | - application/xml 22 | Content-Length: 23 | - '217' 24 | User-Agent: 25 | - Ruby 26 | response: 27 | status: 28 | code: 201 29 | message: Created 30 | headers: 31 | Server: 32 | - Apache-Coyote/1.1 33 | Location: 34 | - https://api.echo.nasa.gov/echo-rest/tokens/echo-token-here?clientId=unknown 35 | Cache-Control: 36 | - no-cache 37 | X-Ua-Compatible: 38 | - IE=Edge,chrome=1 39 | Set-Cookie: 40 | - _EchoRestRails_session=BAh7BiIPc2Vzc2lvbl9pZCIlNGRmMTA4Y2U5YjJjMjU5YjI2YjI4NDA2NjM5NTU0NDM%3D--c0c17f33fed4f1c4590ccf06c61d4195d88e168f; path=/; HttpOnly 41 | X-Runtime: 42 | - '0.093000' 43 | Content-Type: 44 | - application/xml;charset=utf-8 45 | Content-Length: 46 | - '222' 47 | Date: 48 | - Fri, 18 Jul 2014 13:20:26 GMT 49 | body: 50 | encoding: US-ASCII 51 | string: | 52 | 53 | 54 | FDC12C98-533C-F4AA-4169-B40C957117D9 55 | guest 56 | foo-opensearch-dev 57 | 0.0.0.0 58 | 59 | http_version: 60 | recorded_at: Fri, 18 Jul 2014 13:20:26 GMT 61 | recorded_with: VCR 2.9.2 62 | -------------------------------------------------------------------------------- /features/fixtures/cucumber_tags/granules_search_validation_atom_old.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api.echo.nasa.gov/echo-rest//tokens 6 | body: 7 | encoding: US-ASCII 8 | string: |- 9 | 10 | guest 11 | guest@echo.nasa.gov 12 | foo-opensearch-dev 13 | 0.0.0.0 14 | 15 | headers: 16 | Accept: 17 | - '*/*; q=0.5, application/xml' 18 | Accept-Encoding: 19 | - gzip, deflate 20 | Content-Type: 21 | - application/xml 22 | Content-Length: 23 | - '217' 24 | User-Agent: 25 | - Ruby 26 | response: 27 | status: 28 | code: 201 29 | message: Created 30 | headers: 31 | Server: 32 | - Apache-Coyote/1.1 33 | Location: 34 | - https://api.echo.nasa.gov/echo-rest/tokens/echo-token-here?clientId=unknown 35 | Cache-Control: 36 | - no-cache 37 | X-Ua-Compatible: 38 | - IE=Edge,chrome=1 39 | Set-Cookie: 40 | - _EchoRestRails_session=BAh7BiIPc2Vzc2lvbl9pZCIlZjZjMmQ0YWRhZGYwMWI4NWY2ODcwNzFjZjYwMjA4Yjc%3D--dd8563de531f7d8d4740e2e0faa7c66112b66a69; path=/; HttpOnly 41 | X-Runtime: 42 | - '0.060000' 43 | Content-Type: 44 | - application/xml;charset=utf-8 45 | Content-Length: 46 | - '222' 47 | Date: 48 | - Fri, 18 Jul 2014 13:45:27 GMT 49 | body: 50 | encoding: US-ASCII 51 | string: | 52 | 53 | 54 | AAFFD49A-251A-383D-B6EA-E03D526C5497 55 | guest 56 | foo-opensearch-dev 57 | 0.0.0.0 58 | 59 | http_version: 60 | recorded_at: Fri, 18 Jul 2014 13:45:27 GMT 61 | recorded_with: VCR 2.9.2 62 | -------------------------------------------------------------------------------- /features/granules/atom/search_dc_temporal.feature: -------------------------------------------------------------------------------- 1 | @granules_search_dc_temporal_atom 2 | Feature: Retrieve granules in atom format with dublin core temporal extents 3 | In order to obtain granule products 4 | as an open search user 5 | I should be able to search for granules using a machine-readable format that renders temporal extents compliant with the dublin-core (http://purl.org/dc/elements/1.1/) date element 6 | # Example: 2010-04- 13T20:38:16.000Z/2010-04-13T20:40:00.000Z 7 | 8 | Scenario: Search for granule with date range 9 | Given I have executed a granule search with the following parameters: 10 | | clientId | startTime | endTime | dataCenter | 11 | | foo | 1951-01-01T00:00:00Z | 1952-12-01T00:00:00Z | OS_PROV_2 | 12 | Then I should see a valid granule atom response 13 | And result 1 should have a dublin core temporal extent of "1951-01-01T00:00:00Z/1952-12-01T00:00:00Z" 14 | And result 1 should not have an open search time temporal extent 15 | 16 | Scenario: Search for dataset with date start 17 | Given I have executed a granule search with the following parameters: 18 | | clientId | startTime | endTime | dataCenter | 19 | | foo | 1953-01-01T00:00:00Z | 1954-01-01T00:00:00Z | OS_PROV_2 | 20 | Then I should see a valid granule atom response 21 | And result 1 should have a dublin core temporal extent of "1953-01-01T00:00:00Z/" 22 | And result 1 should not have an open search time temporal extent 23 | 24 | Scenario: Search for dataset with date single 25 | Given I have executed a granule search with the following parameters: 26 | | clientId | startTime | endTime | dataCenter | 27 | | foo | 1945-01-01T22:00:00Z | 1945-02-01T22:00:00Z | OS_PROV_2 | 28 | Then I should see a valid granule atom response 29 | And result 1 should have a dublin core temporal extent of "1945-01-01T22:00:00Z" 30 | And result 1 should not have an open search time temporal extent 31 | -------------------------------------------------------------------------------- /spec/helpers/granules_helpers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe GranulesHelper do 4 | describe 'determine_granule_url' do 5 | it 'should return a url from related urls when it exists' do 6 | graphql_metadata = { 7 | 'id' => 'C0000000001-OPENSEARCH', 8 | 'relatedUrls' => [{ 9 | 'urlContentType' => 'DistributionURL', 10 | 'subtype' => 'OpenSearch', 11 | 'url' => 'https://cmr.earthdata.nasa.gov/opensearch/atom.xml?datasetId=fromRelatedUrls' 12 | }] 13 | } 14 | 15 | granule_url = determine_granule_url(graphql_metadata) 16 | 17 | expect(granule_url).to eq('https://cmr.earthdata.nasa.gov/opensearch/atom.xml?datasetId=fromRelatedUrls') 18 | end 19 | 20 | it 'should return a url from tags when it exists and related urls do not' do 21 | graphql_metadata = { 22 | 'id' => 'C0000000001-OPENSEARCH', 23 | 'tags' => { 24 | 'opensearch.granule.osdd' => { 25 | 'data' => 'https://cmr.earthdata.nasa.gov/opensearch/atom.xml?datasetId=fromTags' 26 | } 27 | } 28 | } 29 | 30 | granule_url = determine_granule_url(graphql_metadata) 31 | 32 | expect(granule_url).to eq('https://cmr.earthdata.nasa.gov/opensearch/atom.xml?datasetId=fromTags') 33 | end 34 | 35 | it 'should return a url from related urls when it exists but tags also exist' do 36 | graphql_metadata = { 37 | 'id' => 'C0000000001-OPENSEARCH', 38 | 'relatedUrls' => [{ 39 | 'urlContentType' => 'DistributionURL', 40 | 'subtype' => 'OpenSearch', 41 | 'url' => 'https://cmr.earthdata.nasa.gov/opensearch/atom.xml?datasetId=fromRelatedUrls' 42 | }], 43 | 'tags' => { 44 | 'opensearch.granule.osdd' => { 45 | 'data' => 'https://cmr.earthdata.nasa.gov/opensearch/atom.xml?datasetId=fromTags' 46 | } 47 | } 48 | } 49 | 50 | granule_url = determine_granule_url(graphql_metadata) 51 | 52 | expect(granule_url).to eq('https://cmr.earthdata.nasa.gov/opensearch/atom.xml?datasetId=fromRelatedUrls') 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CMR OpenSearch 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <%= javascript_include_tag 'application' %> 14 | <%= stylesheet_link_tag 'application', :media => 'all' %> 15 | <%= csrf_meta_tags %> 16 | 17 | 18 |
19 | 22 |
23 |
24 |

OpenSearch

25 | 26 |

Using the NASA EOSDIS Common Metadata Repository

27 |
28 |
29 |
30 | 31 |
32 | 33 | <% unless flash[:error].blank? %> 34 |
35 | Unable to process request : <%= flash[:error] %> 36 |
37 | <% end %> 38 | <%= yield %> 39 |
40 |
41 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /spec/fixtures/views/collection/navigation_32287_offset.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://cmr.earthdata.nasa.gov/search/collections.atom?include_has_granules=true&include_tags=org.ceos.wgiss.cwic.*,opensearch.granule.osdd,org.geoss.geoss_data-core,gov.nasa.eosdis,int.esa.fedeo&offset=32287&page_size=10 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - "*/*; q=0.5, application/xml" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | Client-Id: 15 | - cmr-open-search 16 | User-Agent: 17 | - Ruby 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Date: 24 | - Wed, 28 Sep 2016 19:02:11 GMT 25 | Content-Type: 26 | - application/atom+xml; charset=utf-8 27 | Access-Control-Expose-Headers: 28 | - CMR-Hits, CMR-Request-Id 29 | Access-Control-Allow-Origin: 30 | - "*" 31 | Cmr-Hits: 32 | - '32287' 33 | Cmr-Took: 34 | - '38' 35 | Cmr-Request-Id: 36 | - c55de27d-4042-4fc5-8940-ab032818c508 37 | Content-Length: 38 | - '742' 39 | Server: 40 | - Jetty(9.2.z-SNAPSHOT) 41 | body: 42 | encoding: UTF-8 43 | string: 2016-09-28T19:02:11.590Zhttps://cmr.earthdata.nasa.gov:443/search/collections.atom?include_has_granules=true&include_tags=org.ceos.wgiss.cwic.%2A%2Corg.geoss.geoss_data-core&page_size=10&offset=32287ECHO dataset metadata 49 | http_version: 50 | recorded_at: Wed, 28 Sep 2016 19:02:11 GMT 51 | recorded_with: VCR 2.9.2 52 | -------------------------------------------------------------------------------- /spec/fixtures/views/collection/navigation_32288_offset.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://cmr.earthdata.nasa.gov/search/collections.atom?include_has_granules=true&include_tags=org.ceos.wgiss.cwic.*,opensearch.granule.osdd,org.geoss.geoss_data-core,gov.nasa.eosdis,int.esa.fedeo&offset=32288&page_size=10 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - "*/*; q=0.5, application/xml" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | Client-Id: 15 | - cmr-open-search 16 | User-Agent: 17 | - Ruby 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Date: 24 | - Wed, 28 Sep 2016 19:05:26 GMT 25 | Content-Type: 26 | - application/atom+xml; charset=utf-8 27 | Access-Control-Expose-Headers: 28 | - CMR-Hits, CMR-Request-Id 29 | Access-Control-Allow-Origin: 30 | - "*" 31 | Cmr-Hits: 32 | - '32287' 33 | Cmr-Took: 34 | - '90' 35 | Cmr-Request-Id: 36 | - 10106a1d-dfe1-42da-ad3d-bc297989ac7e 37 | Content-Length: 38 | - '742' 39 | Server: 40 | - Jetty(9.2.z-SNAPSHOT) 41 | body: 42 | encoding: UTF-8 43 | string: 2016-09-28T19:05:27.029Zhttps://cmr.earthdata.nasa.gov:443/search/collections.atom?include_has_granules=true&include_tags=org.ceos.wgiss.cwic.%2A%2Corg.geoss.geoss_data-core&page_size=10&offset=32288ECHO dataset metadata 49 | http_version: 50 | recorded_at: Wed, 28 Sep 2016 19:05:26 GMT 51 | recorded_with: VCR 2.9.2 52 | -------------------------------------------------------------------------------- /spec/models/ceos_agency_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe CeosAgency do 4 | describe "CEOS Agency" do 5 | it "is possible to create a CEOS Agency from a JSON string" do 6 | json_ceos_agency_string = ' 7 | { 8 | "name": "ceos_agency_1", 9 | "data_centers": ["DC1", "DC2"] 10 | }' 11 | c = CeosAgency.new(json_ceos_agency_string) 12 | expect(c.name).to eq('ceos_agency_1') 13 | expect(c.data_centers).to match_array(["DC1", "DC2"]) 14 | end 15 | end 16 | 17 | it "is possible to create two CEOS Agencies from a JSON string" do 18 | json_ceos_agencies_array = 19 | [ 20 | '{ 21 | "name": "ceos_agency_1", 22 | "data_centers": ["DC1", "DC2"] 23 | }', 24 | '{ 25 | "name": "ceos_agency_2", 26 | "data_centers": ["DC3", "DC4"] 27 | }' 28 | ] 29 | ceos_agencies_array = CeosAgency.create_all_ceos_agencies(json_ceos_agencies_array) 30 | expect(ceos_agencies_array.length).to eq(2) 31 | first_agency = ceos_agencies_array[0] 32 | second_agency = ceos_agencies_array[1] 33 | expect(first_agency.name).to eq('ceos_agency_1') 34 | expect(first_agency.data_centers).to match_array(["DC1", "DC2"]) 35 | expect(second_agency.name).to eq('ceos_agency_2') 36 | expect(second_agency.data_centers).to match_array(["DC3", "DC4"]) 37 | end 38 | 39 | it "is possible to create a CMR query string from a JSON string" do 40 | json_ceos_agencies_array = 41 | [ 42 | '{ 43 | "name": "ceos_agency_1", 44 | "data_centers": ["DC1", "DC2"] 45 | }', 46 | '{ 47 | "name": "ceos_agency_2", 48 | "data_centers": ["DC3", "DC4"] 49 | }' 50 | ] 51 | escaped_cmr_query_string = CeosAgency.create_all_ceos_agencies_cmr_query_string(json_ceos_agencies_array).to_query 52 | unescaped_cmr_query_string = URI::decode_www_form_component(escaped_cmr_query_string) 53 | expected_unescaped_query_string = 'data_center[]=DC1&data_center[]=DC2&data_center[]=DC3&data_center[]=DC4&options[data_center][pattern]=true' 54 | expect(unescaped_cmr_query_string).to eq(expected_unescaped_query_string) 55 | end 56 | end -------------------------------------------------------------------------------- /spec/services/eosdis_tagger_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe EosdisTagger do 4 | 5 | before(:all) do 6 | @tagger = EosdisTagger.new 7 | end 8 | 9 | it 'should tag EOSDIS collections with one POST per provider' do 10 | VCR.use_cassette 'services/eosdis_collections', :record => :once, :decode_compressed_response => true do 11 | @tagger.execute_scheduled_task 12 | verify_tagged_collection('C179003030-ORNL_DAAC') 13 | verify_tagged_collections_count(6526) 14 | end 15 | end 16 | 17 | it 'should tag EOSDIS collections with one POST for all providers' do 18 | VCR.use_cassette 'services/eosdis_collections_one_post', :record => :once, :decode_compressed_response => true do 19 | @tagger.execute_scheduled_task_single_post 20 | verify_tagged_collection('C179003030-ORNL_DAAC') 21 | verify_tagged_collections_count(6525) 22 | end 23 | end 24 | 25 | 26 | private 27 | def verify_tagged_collection(concept_id) 28 | response = RestClient::Request.execute :method => :get, :url => "#{@tagger.catalog_rest_endpoint}collections", 29 | headers: {params: {:concept_id => concept_id, :tag_key => @tagger.tag}}, 30 | :verify_ssl => OpenSSL::SSL::VERIFY_NONE 31 | if (response.code != 200) 32 | msg = "ERROR #{response.code} retrieving tag: #{@tagger.tag}" 33 | Rails.logger.info(msg) 34 | fail msg 35 | else 36 | response_xml = Nokogiri::XML(response.body) 37 | # must only get 1 hit 38 | expect('1').to eq(response_xml.xpath('results/hits').first.text) 39 | # must get the expected concept_id 40 | expect(concept_id).to eq(response_xml.xpath('results/references/reference/id').first.text) 41 | end 42 | end 43 | 44 | def verify_tagged_collections_count(expected_count) 45 | response = RestClient::Request.execute :method => :get, :url => "#{@tagger.catalog_rest_endpoint}collections", 46 | headers: {params: {tag_key: @tagger.tag}}, 47 | :verify_ssl => OpenSSL::SSL::VERIFY_NONE 48 | hits = response.headers[:cmr_hits].to_i 49 | expect(hits).to eq(expected_count) 50 | end 51 | end -------------------------------------------------------------------------------- /spec/views/granules/required_form_params_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'granule search form test' do 4 | include Capybara::DSL 5 | 6 | it 'does not allow granule searches with empty UniqueID and ShortName and CollectionID' do 7 | VCR.use_cassette 'views/granule/allRequiredFormParamsEmpty', :record => :once, :decode_compressed_response => true do 8 | visit granules_path 9 | click_button('Search') 10 | expect(page).to have_content("3 errors prohibited this search from being executed:") 11 | expect(page).to have_content("Short name : A granule search requires the Collection ConceptID or the Collection ShortName or the Granule Unique Identifier") 12 | expect(page).to have_content("Collection concept ID : A granule search requires the Collection ConceptID or the Collection ShortName or the Granule Unique Identifier") 13 | expect(page).to have_content("Unique ID : A granule search requires the Collection ConceptID or the Collection ShortName or the Granule Unique Identifier") 14 | end 15 | end 16 | 17 | it 'does allows granule searches with only the ShortName parameter' do 18 | VCR.use_cassette 'views/granule/ShortNameParam', :record => :once, :decode_compressed_response => true do 19 | visit granules_path 20 | fill_in('shortName', :with => 'MOD02HKM') 21 | click_button('Search') 22 | expect(page).to have_content("Displaying 1 to 10 of 1024641") 23 | end 24 | end 25 | 26 | it 'does allows granule searches with only the CollectionConceptId parameter' do 27 | VCR.use_cassette 'views/granule/CollectionConceptIDParam', :record => :once, :decode_compressed_response => true do 28 | visit granules_path 29 | fill_in('parentIdentifier', :with => 'C203234490-LAADS') 30 | click_button('Search') 31 | expect(page).to have_content("Displaying 1 to 10 of 1023537") 32 | end 33 | end 34 | 35 | it 'does allows granule searches with only the UniqueID parameter' do 36 | VCR.use_cassette 'views/granule/UniqueIDParam', :record => :once, :decode_compressed_response => true do 37 | visit granules_path 38 | fill_in('uid', :with => 'G240103476-LAADS') 39 | click_button('Search') 40 | expect(page).to have_content("Displaying 1 to 1 of 1") 41 | end 42 | end 43 | 44 | end 45 | -------------------------------------------------------------------------------- /spec/fixtures/services/place_name_to_geometry.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: http://api.geonames.org/searchJSON?maxRows=1&q=dougopolis&username=echo_reverb 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - '*/*' 12 | User-Agent: 13 | - Ruby 14 | response: 15 | status: 16 | code: 200 17 | message: OK 18 | headers: 19 | Date: 20 | - Fri, 18 Jul 2014 16:44:00 GMT 21 | Server: 22 | - Apache/2.4.6 (Linux/SUSE) 23 | Cache-Control: 24 | - no-cache 25 | Access-Control-Allow-Origin: 26 | - '*' 27 | Transfer-Encoding: 28 | - chunked 29 | Content-Type: 30 | - application/json;charset=UTF-8 31 | Connection: 32 | - Keep-Alive 33 | body: 34 | encoding: US-ASCII 35 | string: '{"totalResultsCount":0,"geonames":[]}' 36 | http_version: 37 | recorded_at: Fri, 18 Jul 2014 16:44:00 GMT 38 | - request: 39 | method: get 40 | uri: http://api.geonames.org/searchJSON?maxRows=1&q=Bowness-on-solway&username=echo_reverb 41 | body: 42 | encoding: US-ASCII 43 | string: '' 44 | headers: 45 | Accept: 46 | - '*/*' 47 | User-Agent: 48 | - Ruby 49 | response: 50 | status: 51 | code: 200 52 | message: OK 53 | headers: 54 | Date: 55 | - Fri, 18 Jul 2014 16:44:01 GMT 56 | Server: 57 | - Apache/2.4.6 (Linux/SUSE) 58 | Cache-Control: 59 | - no-cache 60 | Access-Control-Allow-Origin: 61 | - '*' 62 | Transfer-Encoding: 63 | - chunked 64 | Content-Type: 65 | - application/json;charset=UTF-8 66 | Connection: 67 | - Keep-Alive 68 | body: 69 | encoding: US-ASCII 70 | string: '{"totalResultsCount":2,"geonames":[{"adminCode1":"ENG","lng":"-3.21187","geonameId":2655050,"toponymName":"Bowness-on-Solway","countryId":"2635167","fcl":"P","population":0,"countryCode":"GB","name":"Bowness-on-Solway","fclName":"city, 71 | village,...","countryName":"United Kingdom","fcodeName":"populated place","adminName1":"England","lat":"54.95389","fcode":"PPL"}]}' 72 | http_version: 73 | recorded_at: Fri, 18 Jul 2014 16:44:01 GMT 74 | recorded_with: VCR 2.9.2 75 | -------------------------------------------------------------------------------- /features/collections/atom/search_dc_temporal.feature: -------------------------------------------------------------------------------- 1 | @datasets_search_dc_temporal_atom 2 | Feature: Retrieve collections in atom format with dublin core temporal extents 3 | In order to obtain collection products 4 | as an open search user 5 | I should be able to search for collections using a machine-readable format that renders temporal extents compliant with the dublin-core (http://purl.org/dc/elements/1.1/) date element 6 | # Example: 2010-04- 13T20:38:16.000Z/2010-04-13T20:40:00.000Z 7 | 8 | Scenario: Search for collection with date range 9 | Given I have executed a collection search with the following parameters: 10 | | clientId | keyword | 11 | | foo | Dublin Core range dataset | 12 | Then I should see a valid collection atom response 13 | And result 1 should have a dublin core temporal extent of "1951-01-01T00:00:00Z/1952-12-01T00:00:00Z" 14 | And result 1 should not have an open search time temporal extent 15 | 16 | Scenario: Search for collection with date start 17 | Given I have executed a collection search with the following parameters: 18 | | clientId | keyword | 19 | | foo | Dublin Core start dataset | 20 | Then I should see a valid collection atom response 21 | And result 1 should have a dublin core temporal extent of "1953-01-01T00:00:00Z/" 22 | And result 1 should not have an open search time temporal extent 23 | 24 | Scenario: Search for collection with date end 25 | Given I have executed a collection search with the following parameters: 26 | | clientId | keyword | 27 | | foo | Dublin Core single dataset | 28 | Then I should see a valid collection atom response 29 | And result 1 should have a dublin core temporal extent of "1955-01-01T22:00:00Z" 30 | And result 1 should not have an open search time temporal extent 31 | 32 | Scenario: Search for collection with no temporal extent 33 | Given I have executed a collection search with the following parameters: 34 | | clientId | keyword | 35 | | foo | Dublin Core none dataset | 36 | Then I should see a valid collection atom response 37 | And result 1 should have no dublin core temporal extent 38 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | EchoOpensearch::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static file server for tests with Cache-Control for performance. 16 | config.serve_static_files = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Randomize the order test cases are executed. 35 | config.active_support.test_order = :random 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | config.relative_url_root = '' 41 | 42 | config.graphql_endpoint = ENV['GRAPHQL_ENDPOINT'] 43 | # config.cache_store = :memory_store, { size: 64.megabytes } 44 | config.cache_store = :null_store 45 | 46 | config.holdings_providers = [ 47 | { 'provider' => 'TEST', 'params' => { 'dataCenter' => 'test' } } 48 | ] 49 | 50 | Rails.logger = Logger.new("#{Rails.root}/log/#{Rails.env}.log") 51 | Rails.logger.formatter = Logger::Formatter.new 52 | 53 | puts 'This is a test deployment' 54 | end 55 | -------------------------------------------------------------------------------- /spec/views/granules/esip_bp_up_link_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'ESIP Best Practices up link from granule search result set' do 4 | include Rack::Test::Methods 5 | 6 | def app 7 | Rails.application 8 | end 9 | 10 | it 'correctly creates an up link for granule search results which contain granules from a single data set' do 11 | VCR.use_cassette 'views/granule/correct_up_link', :record => :once do 12 | granule_opensearch_response = get '/granules.atom?shortName=MODISA_L3m_CHL&versionId=2014&dataCenter=OB_DAAC&startTime=2005-01-01T00%3A00%3A01Z&endTime=2005-01-01T23%3A59%3A59Z&clientId=goodUplink' 13 | assert granule_opensearch_response.ok? 14 | feed = Nokogiri::XML(granule_opensearch_response.body) 15 | feed_up_link = feed.xpath('//atom:feed/atom:link[@rel="up"]', 'atom' => 'http://www.w3.org/2005/Atom') 16 | assert_equal 1, feed_up_link.size 17 | end 18 | end 19 | 20 | it 'does not create an up link for granule search results which contain granules from multiple data sets' do 21 | VCR.use_cassette 'views/granule/no_gran_up_link', :record => :once do 22 | granule_opensearch_response = get '/granules.atom?dataCenter=OB_DAAC&&startTime=2005-01-01T00%3A00%3A01Z&endTime=2005-01-01T23%3A59%3A59Z&clientId=noGranUplink' 23 | assert granule_opensearch_response.ok? 24 | feed = Nokogiri::XML(granule_opensearch_response.body) 25 | feed_root_links = feed.xpath('//atom:feed/atom:link', 'atom' => 'http://www.w3.org/2005/Atom') 26 | feed_root_links.each do |link| 27 | rel = link['rel'] 28 | expect(rel).not_to equal('up') 29 | end 30 | end 31 | end 32 | 33 | it 'does not create an up link for dataset search results' do 34 | VCR.use_cassette 'views/granule/no_ds_up_link', :record => :once do 35 | granule_opensearch_response = get '/datasets.atom?keyword=MODIS&startTime=2011-01-01T00:00:00Z&endTime=2011-01-02T00:00:00Z&clientId=noDatasetsUplink' 36 | assert granule_opensearch_response.ok? 37 | feed = Nokogiri::XML(granule_opensearch_response.body) 38 | feed_root_links = feed.xpath('//atom:feed/atom:link', 'atom' => 'http://www.w3.org/2005/Atom') 39 | feed_root_links.each do |link| 40 | rel = link['rel'] 41 | expect(rel).not_to equal('up') 42 | end 43 | end 44 | end 45 | end -------------------------------------------------------------------------------- /app/views/granules/nrsc.xml.erb: -------------------------------------------------------------------------------- 1 | 2 | 7 | NRSC OpenSearch 8 | NRSC OpenSearch Services for IRS Data 9 | uops_designer@nrsc.gov.in 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ISRO 23 | User Order Processing System Development Team 24 | UTF-8 25 | en-us 26 | UTF-8 27 | open 28 | NRSC,ISRO,OpenSearch 29 | -------------------------------------------------------------------------------- /features/support/cuke_env.rb: -------------------------------------------------------------------------------- 1 | # These lines allow us to run cucumber with the -q option, which doesn't load env.rb 2 | unless defined? ENV_LOADED 3 | ENV_LOADED = defined? ENV 4 | ENV ||= {'rest_root' => ''} 5 | end 6 | 7 | module CukeEnv 8 | def self.browser 9 | if ENV["browser"] 10 | @browser ||= ENV["browser"].to_sym 11 | else 12 | @browser ||= :firefox 13 | end 14 | end 15 | 16 | def self.host 17 | @host ||= (ENV["host"] or "localhost") 18 | end 19 | 20 | def self.port 21 | @port ||= (ENV["port"] or "3000") 22 | end 23 | 24 | def self.rails_root 25 | @rails_root ||= (ENV["rails_root"] or "") 26 | end 27 | 28 | def self.capybara_port 29 | (ENV["capybara_port"] && ENV["capybara_port"].to_i)|| 9887 30 | end 31 | 32 | # Returns true if fixtures should be loaded before starting cucumber tests 33 | # Defaults to false 34 | def self.load_fixtures? 35 | ENV["load_fixtures"] || ENV["reload_fixtures"] 36 | end 37 | 38 | def self.reload_fixtures? 39 | ENV["reload_fixtures"] 40 | end 41 | 42 | # Returns true if a cleanup should be performed before running cucumber tests. 43 | def self.cleanup_first? 44 | ENV["cleanup"] == "true" 45 | end 46 | 47 | def self.env_loaded? 48 | ENV_LOADED 49 | end 50 | 51 | #Returns the base URL of the application being tested. 52 | def self.url_base(include_rails_root = true) 53 | url = "http://#{host}:#{port}" 54 | if include_rails_root 55 | url = "#{url}#{rails_root}" 56 | end 57 | url 58 | end 59 | 60 | def self.rest_url_base(include_rails_root = true) 61 | url = ENV["rest_root"] 62 | end 63 | 64 | #Returns the format of the messages being tested as a symbol. Either :json or :xml 65 | def self.message_format 66 | if !@message_format 67 | format = (ENV["msg_format"] or "json") 68 | @message_format = format.to_sym 69 | end 70 | @message_format 71 | end 72 | 73 | def self.message_format_mime_type 74 | if message_format == :json 75 | return "application/json" 76 | elsif message_format == :xml 77 | return "application/xml" 78 | else 79 | raise "Unknown message format #{message_format}" 80 | end 81 | end 82 | 83 | def self.decode_from_format(value) 84 | if message_format == :json 85 | ActiveSupport::JSON.decode(value) 86 | elsif message_format == :xml 87 | Hash.from_xml(value) 88 | else 89 | raise "Unknown message format #{message_format}" 90 | end 91 | end 92 | 93 | end 94 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | EchoOpensearch::Application.routes.draw do 2 | 3 | get 'health' => 'health#index', format: 'json' 4 | 5 | get 'home/index' 6 | 7 | get 'home/docs' 8 | 9 | get 'docs', to: 'home#docs' 10 | 11 | resources :collections do 12 | member do 13 | 14 | end 15 | collection do 16 | get 'descriptor_document' 17 | get 'descriptor_document_facets' 18 | end 19 | end 20 | 21 | resources :granules do 22 | collection do 23 | get 'descriptor_document' 24 | end 25 | end 26 | 27 | resources :collections, :path => 'datasets' 28 | 29 | resources :holdings, only: [:index, :show], path: 'api/holdings' 30 | 31 | # The priority is based upon order of creation: 32 | # first created -> highest priority. 33 | 34 | # Sample of regular route: 35 | # match 'products/:id' => 'catalog#view' 36 | # Keep in mind you can assign values other than :controller and :action 37 | 38 | # Sample of named route: 39 | # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase 40 | # This route can be invoked with purchase_url(:id => product.id) 41 | 42 | # Sample resource route (maps HTTP verbs to controller actions automatically): 43 | # resources :products 44 | 45 | # Sample resource route with options: 46 | # resources :products do 47 | # member do 48 | # get 'short' 49 | # post 'toggle' 50 | # end 51 | # 52 | # collection do 53 | # get 'sold' 54 | # end 55 | # end 56 | 57 | # Sample resource route with sub-resources: 58 | # resources :products do 59 | # resources :comments, :sales 60 | # resource :seller 61 | # end 62 | 63 | # Sample resource route with more complex sub-resources 64 | # resources :products do 65 | # resources :comments 66 | # resources :sales do 67 | # get 'recent', :on => :collection 68 | # end 69 | # end 70 | 71 | # Sample resource route within a namespace: 72 | # namespace :admin do 73 | # # Directs /admin/products/* to Admin::ProductsController 74 | # # (app/controllers/admin/products_controller.rb) 75 | # resources :products 76 | # end 77 | 78 | # You can have the root of your site routed with "root" 79 | # just remember to delete public/index.html. 80 | root :to => 'home#index' 81 | 82 | # See how all your routes lay out with "rake routes" 83 | 84 | # This is a legacy wild controller route that's not recommended for RESTful applications. 85 | # Note: This route will make all actions in every controller accessible via GET requests. 86 | # match ':controller(/:action(/:id))(.:format)' 87 | end 88 | -------------------------------------------------------------------------------- /lib/tasks/cucumber.rake: -------------------------------------------------------------------------------- 1 | # IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. 2 | # It is recommended to regenerate this file in the future when you upgrade to a 3 | # newer version of cucumber-rails. Consider adding your own code to a new file 4 | # instead of editing this one. Cucumber will automatically load all features/**/*.rb 5 | # files. 6 | 7 | 8 | unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks 9 | 10 | vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first 11 | $LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? 12 | 13 | begin 14 | require 'cucumber/rake/task' 15 | 16 | namespace :cucumber do 17 | Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t| 18 | t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. 19 | t.fork = true # You may get faster startup if you set this to false 20 | t.profile = 'default' 21 | end 22 | 23 | Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t| 24 | t.binary = vendored_cucumber_bin 25 | t.fork = true # You may get faster startup if you set this to false 26 | t.profile = 'wip' 27 | end 28 | 29 | Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t| 30 | t.binary = vendored_cucumber_bin 31 | t.fork = true # You may get faster startup if you set this to false 32 | t.profile = 'rerun' 33 | end 34 | 35 | desc 'Run all features' 36 | task :all => [:ok, :wip] 37 | 38 | task :statsetup do 39 | require 'rails/code_statistics' 40 | ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features') 41 | ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features') 42 | end 43 | end 44 | desc 'Alias for cucumber:ok' 45 | task :cucumber => 'cucumber:ok' 46 | 47 | task :default => :cucumber 48 | 49 | task :features => :cucumber do 50 | STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" 51 | end 52 | 53 | # In case we don't have ActiveRecord, append a no-op task that we can depend upon. 54 | task 'db:test:prepare' do 55 | end 56 | 57 | task :stats => 'cucumber:statsetup' 58 | rescue LoadError 59 | desc 'cucumber rake task not available (cucumber not installed)' 60 | task :cucumber do 61 | abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' 62 | end 63 | end 64 | 65 | end 66 | -------------------------------------------------------------------------------- /app/assets/stylesheets/results.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | */ 13 | p.cc-meta-detail, div.cc-meta { 14 | span.value { 15 | color: rgb(50, 50, 50); 16 | } 17 | span.label { 18 | color: rgb(50, 50, 50); 19 | font-weight: bold; 20 | } 21 | 22 | div.group-meta { 23 | padding-left: 1em; 24 | } 25 | div.spacer { 26 | padding-bottom: 1em; 27 | } 28 | 29 | .granule-search { 30 | float:right; 31 | margin-bottom:1em; 32 | margin-right:1em 33 | } 34 | } 35 | 36 | button.granule-search { 37 | float: right; 38 | margin-bottom: 1em; 39 | margin-right: 1em; 40 | } 41 | 42 | div.thumb-container { 43 | ul.optional-links { 44 | background-color: inherit; 45 | li { 46 | background-color: inherit; 47 | font-weight: normal; 48 | } 49 | } 50 | } 51 | 52 | .temporal-input { 53 | position: relative; 54 | font-family: sans-serif; 55 | } 56 | 57 | section#results { 58 | height: 40em; 59 | font-weight: 200; 60 | overflow: scroll; 61 | background-color: white; 62 | ul.results { 63 | margin-top: 0em; 64 | margin-bottom: 0em; 65 | padding-top: 0em; 66 | padding-bottom: 0em; 67 | padding-left: 0em; 68 | list-style-type: none; 69 | li.result { 70 | background-color: rgb(236, 240, 241); 71 | 72 | padding-left: 1em; 73 | padding-top: 1em; 74 | margin-top: 0em; 75 | margin-bottom: 0em; 76 | &.alt { 77 | background-color: rgb(149, 165, 166); 78 | } 79 | } 80 | } 81 | } 82 | 83 | .content-capsule { 84 | h3 { 85 | margin-left: 0em; 86 | } 87 | border-bottom-width: 0px; 88 | li { 89 | list-style-type: none; 90 | list-style-image: none; 91 | list-style-position: outside; 92 | text-align: left; 93 | font-size: 1em; 94 | background-color: inherit; 95 | font-weight: normal; 96 | } 97 | p.cc-meta-detail { 98 | margin-left: 0em; 99 | } 100 | } 101 | 102 | #time_start_picker { 103 | position: relative; 104 | } 105 | 106 | #time_end_picker { 107 | position: relative; 108 | } -------------------------------------------------------------------------------- /app/models/ceos_agency.rb: -------------------------------------------------------------------------------- 1 | class CeosAgency 2 | attr_accessor :name, :data_centers 3 | 4 | # JSON string format is: 5 | # ' 6 | #{ 7 | # "name": "ceos_agency_11", 8 | # "data_centers": ["DC1", "DC2"] 9 | #}' 10 | def initialize(json_string) 11 | json_hash = JSON.parse(json_string) 12 | @name = json_hash['name'] 13 | @data_centers = json_hash['data_centers'] 14 | end 15 | 16 | # JSON array format is: 17 | #[ 18 | #'{ 19 | # "name": "ceos_agency_1", 20 | # "data_centers": ["DC1", "DC2"] 21 | # }', 22 | # '{ 23 | # "name": "ceos_agency_2", 24 | # "data_centers": ["DC3", "DC4"] 25 | # }' 26 | #] 27 | def self.create_all_ceos_agencies_cmr_query_string(array_of_json_strings) 28 | cmr_query_params = {} 29 | query_data_centers = cmr_query_params['data_center'] 30 | #query_archive_centers = cmr_query_params['archive_center'] 31 | ceos_agencies_array = CeosAgency.create_all_ceos_agencies(array_of_json_strings) 32 | if (ceos_agencies_array != nil && !ceos_agencies_array.empty?) 33 | ceos_agencies_array.each do |ceos_agency| 34 | agency_data_centers = ceos_agency.data_centers 35 | if !agency_data_centers.empty? 36 | agency_data_centers.each do |agency_data_center| 37 | if query_data_centers.nil? 38 | query_data_centers = Array.new 39 | end 40 | query_data_centers << agency_data_center 41 | end 42 | end 43 | end 44 | if(query_data_centers != nil && !query_data_centers.empty?) 45 | if cmr_query_params[:data_center].nil? 46 | cmr_query_params[:data_center] = query_data_centers 47 | # add wilcard support for data_center 48 | if cmr_query_params['options[data_center][pattern]'].nil? 49 | cmr_query_params['options[data_center][pattern]'] = true 50 | end 51 | else 52 | cmr_query_params[:data_center].concat(query_data_centers) 53 | end 54 | end 55 | end 56 | return cmr_query_params 57 | end 58 | 59 | # JSON array format is: 60 | #[ 61 | #'{ 62 | # "name": "ceos_agency_1", 63 | # "data_centers": ["DC1", "DC2"] 64 | # }', 65 | # '{ 66 | # "name": "ceos_agency_2", 67 | # "data_centers": ["DC3", "DC4"] 68 | # }' 69 | #] 70 | def self.create_all_ceos_agencies(array_of_ceos_agencies_json_strings) 71 | all_ceos_agencies = Array.new 72 | array_of_ceos_agencies_json_strings.each do |agency_json_string| 73 | agency = CeosAgency.new(agency_json_string) 74 | all_ceos_agencies << agency 75 | end 76 | return all_ceos_agencies 77 | end 78 | end -------------------------------------------------------------------------------- /spec/views/collections/geoss_collections_tagging_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'geoss search behavior' do 4 | include Rack::Test::Methods 5 | 6 | def app 7 | Rails.application 8 | end 9 | 10 | it 'returns a GEOSS tag in the results for a GEOSS collection' do 11 | VCR.use_cassette 'models/tag/cmr_geoss_collections', :decode_compressed_response => true, :record => :once do 12 | get '/datasets.atom?keyword=geoss_test_one&clientId=geoss_test_two', nil, {'Cwic-User' => 'test'} 13 | expect(last_response.ok?).to be true 14 | feed = Nokogiri::XML(last_response.body) 15 | expect(feed.at_xpath('os:feed/os:entry/echo:is_geoss', 'os' => 'http://www.w3.org/2005/Atom', 'echo' => 'https://cmr.earthdata.nasa.gov/search/site/docs/search/api.html#atom').text).to eq('true') 16 | end 17 | end 18 | it 'does not return a GEOSS tag in the results for a non-GEOSS collection' do 19 | VCR.use_cassette 'models/tag/cmr_geoss_collections', :decode_compressed_response => true, :record => :once do 20 | get '/datasets.atom?keyword=geoss_test_two&clientId=geoss_test_two', nil, {'Cwic-User' => 'test'} 21 | expect(last_response.ok?).to be true 22 | feed = Nokogiri::XML(last_response.body) 23 | expect(feed.at_xpath('os:feed/os:entry/echo:is_geoss', 'os' => 'http://www.w3.org/2005/Atom', 'echo' => 'https://cmr.earthdata.nasa.gov/search/site/docs/search/api.html#atom')).to eq(nil) 24 | end 25 | end 26 | 27 | it 'does not return a GEOSS tag in the results for a non-GEOSS collection' do 28 | VCR.use_cassette 'models/tag/cmr_geoss_collections', :decode_compressed_response => true, :record => :once do 29 | get '/datasets.atom?keyword=geoss_test_three&clientId=geoss_test_three', nil, {'Cwic-User' => 'test'} 30 | expect(last_response.ok?).to be true 31 | feed = Nokogiri::XML(last_response.body) 32 | expect(feed.at_xpath('os:feed/os:entry/echo:is_geoss', 'os' => 'http://www.w3.org/2005/Atom', 'echo' => 'https://cmr.earthdata.nasa.gov/search/site/docs/search/api.html#atom')).to eq(nil) 33 | end 34 | end 35 | 36 | it 'does return a GEOSS tag in the results for a GEOSS collection with multiple tags' do 37 | VCR.use_cassette 'models/tag/cmr_geoss_collections', :decode_compressed_response => true, :record => :once do 38 | get '/datasets.atom?keyword=geoss_test_four&clientId=geoss_test_four', nil, {'Cwic-User' => 'test'} 39 | expect(last_response.ok?).to be true 40 | feed = Nokogiri::XML(last_response.body) 41 | expect(feed.at_xpath('os:feed/os:entry/echo:is_geoss', 'os' => 'http://www.w3.org/2005/Atom', 'echo' => 'https://cmr.earthdata.nasa.gov/search/site/docs/search/api.html#atom').text).to eq('true') 42 | end 43 | end 44 | end 45 | 46 | -------------------------------------------------------------------------------- /spec/views/collections/eosdis_collections_tagging_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'eosdis search behavior' do 4 | include Rack::Test::Methods 5 | 6 | def app 7 | Rails.application 8 | end 9 | 10 | it 'returns an EOSDIS tag in the results for a EOSDIS collection' do 11 | 12 | VCR.use_cassette 'models/tag/cmr_esdis_collections', :decode_compressed_response => true, :record => :once do 13 | get '/datasets.atom?keyword=esdis_test_one&clientId=geoss_test_two', nil, {'Cwic-User' => 'test'} 14 | expect(last_response.ok?).to be true 15 | feed = Nokogiri::XML(last_response.body) 16 | expect(feed.at_xpath('os:feed/os:entry/echo:is_eosdis', 'os' => 'http://www.w3.org/2005/Atom', 'echo' => 'https://cmr.earthdata.nasa.gov/search/site/docs/search/api.html#atom').text).to eq('true') 17 | end 18 | 19 | end 20 | 21 | it 'does not return an EOSDIS tag in the results for a non-EOSDIS collection' do 22 | VCR.use_cassette 'models/tag/cmr_esdis_collections', :decode_compressed_response => true, :record => :once do 23 | get '/datasets.atom?keyword=esdis_test_two&clientId=geoss_test_two', nil, {'Cwic-User' => 'test'} 24 | expect(last_response.ok?).to be true 25 | feed = Nokogiri::XML(last_response.body) 26 | expect(feed.at_xpath('os:feed/os:entry/echo:is_eosdis', 'os' => 'http://www.w3.org/2005/Atom', 'echo' => 'https://cmr.earthdata.nasa.gov/search/site/docs/search/api.html#atom')).to eq(nil) 27 | end 28 | end 29 | 30 | it 'does not return am EOSDIS tag in the results for a non-EOSDIS collection' do 31 | VCR.use_cassette 'models/tag/cmr_esdis_collections', :decode_compressed_response => true, :record => :once do 32 | get '/datasets.atom?keyword=esdis_test_three&clientId=geoss_test_three', nil, {'Cwic-User' => 'test'} 33 | expect(last_response.ok?).to be true 34 | feed = Nokogiri::XML(last_response.body) 35 | expect(feed.at_xpath('os:feed/os:entry/echo:is_eosdis', 'os' => 'http://www.w3.org/2005/Atom', 'echo' => 'https://cmr.earthdata.nasa.gov/search/site/docs/search/api.html#atom')).to eq(nil) 36 | end 37 | end 38 | 39 | it 'does return an EOSDIS tag in the results for an EOSDIS collection with multiple tags' do 40 | VCR.use_cassette 'models/tag/cmr_esdis_collections', :decode_compressed_response => true, :record => :once do 41 | get '/datasets.atom?keyword=esdis_test_four&clientId=geoss_test_four', nil, {'Cwic-User' => 'test'} 42 | expect(last_response.ok?).to be true 43 | feed = Nokogiri::XML(last_response.body) 44 | expect(feed.at_xpath('os:feed/os:entry/echo:is_eosdis', 'os' => 'http://www.w3.org/2005/Atom', 'echo' => 'https://cmr.earthdata.nasa.gov/search/site/docs/search/api.html#atom').text).to eq('true') 45 | end 46 | end 47 | end 48 | 49 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | 4 | def log_performance(method, time, number_of_records = nil) 5 | log = "#{method} request with parameters #{params_to_logging(params)} took #{(time.to_f * 1000).round(0)} ms" 6 | unless number_of_records.nil? 7 | log.concat(" and returned #{number_of_records} hit") 8 | log.concat('s') if number_of_records == 0 || number_of_records > 1 9 | end 10 | log 11 | end 12 | 13 | def params_to_logging(params) 14 | params.delete('controller') 15 | params.delete('action') 16 | params.delete('commit') 17 | params.delete('utf8') 18 | params.delete_if {|key, value| value.nil? || (value.kind_of?(String) && value.empty?) } 19 | params 20 | end 21 | 22 | def destroy_token 23 | Rails.logger.info "Destroying Token ID: #{@token_id}" 24 | begin 25 | Token.delete(@token_id) 26 | rescue 27 | 28 | end 29 | end 30 | 31 | protected 32 | 33 | def extract_common_params 34 | @client_id = params[:clientId] 35 | 36 | # If format is html remove spatial constraints that do not match spatial type 37 | if request.format == :html 38 | 39 | @spatial_type = params[:spatial_type] || 'bbox' 40 | 41 | params[:boundingBox] = nil unless @spatial_type == 'bbox' 42 | params[:geometry] = nil unless @spatial_type == 'geometry' 43 | params[:placeName] = nil unless @spatial_type == 'placename' 44 | end 45 | 46 | @number_of_results = params[:numberOfResults].blank? ? 10 : params[:numberOfResults] 47 | params[:numberOfResults] = @number_of_results 48 | @cursor = params[:cursor].blank? ? 1 : params[:cursor] 49 | # do not default offset, the impact to existing code is not worth it 50 | # per CEOS-BP-007, if both offset and cursor are present, offset (startIndex) preceeds cursor (startPage) 51 | @offset = params[:offset] 52 | if !params[:offset].blank? 53 | params[:cursor] = nil 54 | @cursor = nil 55 | end 56 | if !@cursor.blank? 57 | params[:cursor] = @cursor 58 | end 59 | end 60 | 61 | def extract_query_params params 62 | query_params = params.dup 63 | query_params.delete :action 64 | query_params.delete :controller 65 | query_params.delete :format 66 | query_params.delete :utf8 67 | query_params.delete :spatial_type 68 | query_params.delete :commit 69 | query_params.delete :collection_cursor 70 | query_params.delete :collection_number_of_results 71 | 72 | query_params[:dataset_id] = params[:datasetId] 73 | query_params.delete :datasetId 74 | 75 | # Remove facet parameters 76 | query_params.delete :facetLimit 77 | query_params.delete :facetCount 78 | query_params.delete :facetStart 79 | query_params.delete :facetSort 80 | query_params 81 | end 82 | 83 | end 84 | -------------------------------------------------------------------------------- /features/granules/atom/search_validation.feature: -------------------------------------------------------------------------------- 1 | @granules_search_validation_atom 2 | Feature: Retrieve granules in atom format 3 | In order to obtain granule products 4 | as an open search user 5 | I should be warned of any errors in my search constraints 6 | 7 | Scenario: Search for granule using invalid spatial bounding box 8 | Given I have executed a granule search with the following parameters: 9 | | clientId | boundingBox | 10 | | foo | foo | 11 | Then I should see a valid error response 12 | And I should see 1 error 13 | And I should see the error "Boundingbox foo is not a valid boundingBox" 14 | 15 | Scenario: Search for granule using invalid start temporal constraints 16 | Given I have executed a granule search with the following parameters: 17 | | clientId | startTime | endTime | 18 | | foo | foo | 1999-02-01T23:00:00Z | 19 | Then I should see a valid error response 20 | And I should see 1 error 21 | And I should see the error "Starttime foo is not a valid rfc3339 date" 22 | 23 | Scenario: Search for granule using invalid end temporal constraints 24 | Given I have executed a granule search with the following parameters: 25 | | clientId | startTime | endTime | 26 | | foo | 1999-02-01T23:00:00Z | foo | 27 | Then I should see a valid error response 28 | And I should see 1 error 29 | And I should see the error "Endtime foo is not a valid rfc3339 date" 30 | 31 | Scenario: Search for granule using invalid paging 32 | Given I have executed a granule search with the following parameters: 33 | | clientId | numberOfResults | cursor | 34 | | foo | foo | 1 | 35 | Then I should see a valid error response 36 | And I should see 1 error 37 | And I should see the error "Numberofresults is not a number" 38 | And I have executed a granule search with the following parameters: 39 | | clientId | numberOfResults | cursor | 40 | | foo | 2 | foo | 41 | Then I should see a valid error response 42 | And I should see 1 error 43 | And I should see the error "Cursor is not a number" 44 | And I have executed a granule search with the following parameters: 45 | | clientId | numberOfResults | cursor | 46 | | foo | bar | foo | 47 | Then I should see a valid error response 48 | And I should see 2 errors 49 | And I should see the error "Numberofresults is not a number" 50 | And I should see the error "Cursor is not a number" 51 | 52 | Scenario: Search for granule using invalid constraint 53 | Given I have executed a granule search with the following parameters: 54 | | clientId | foo | 55 | | foo | bar | 56 | Then I should see a valid granule atom response 57 | And I should see 10 results 58 | 59 | -------------------------------------------------------------------------------- /features/opensearch_descriptor_documents/granules.feature: -------------------------------------------------------------------------------- 1 | @granules_osdd 2 | Feature: Retrieve granule Open Search Descriptor Document (OSDD) 3 | In order to obtain granule products 4 | as an open search user 5 | I should be able to obtain an open search descriptor document for granules 6 | 7 | Scenario: Generate granule open search descriptor document with client id only 8 | Given I am on the open search home page 9 | And I fill in the "granules" open search descriptor form with a client id of "foo" 10 | And I click on "Generate" within the "granules" form 11 | Then I should see a granule open search descriptor document for client id "foo" 12 | 13 | Scenario: Generate granule open search descriptor document with client id and dataset id 14 | Given I am on the open search home page 15 | And I fill in the "granules" open search descriptor form with a client id of "foo" 16 | And I fill in the "granules" open search descriptor form with a short name of "MOD02QKM" 17 | And I click on "Generate" within the "granules" form 18 | Then I should see a granule open search descriptor document for client id "foo" and short name "MOD02QKM" 19 | 20 | Scenario: Generate granule open search descriptor document with client id, short name and version id 21 | Given I am on the open search home page 22 | And I fill in the "granules" open search descriptor form with a client id of "foo" 23 | And I fill in the "granules" open search descriptor form with a short name of "MOD02QKM" 24 | And I fill in the "granules" open search descriptor form with a version id of "5" 25 | And I click on "Generate" within the "granules" form 26 | Then I should see a granule open search descriptor document for client id "foo" short name "MOD02QKM" and version id "5" 27 | 28 | Scenario: Generate granule open search descriptor document with client id, short name, version id and data center 29 | Given I am on the open search home page 30 | And I fill in the "granules" open search descriptor form with a client id of "foo" 31 | And I fill in the "granules" open search descriptor form with a short name of "MOD02QKM" 32 | And I fill in the "granules" open search descriptor form with a version id of "5" 33 | And I fill in the "granules" open search descriptor form with a data center of "LAADS" 34 | And I click on "Generate" within the "granules" form 35 | Then I should see a granule open search descriptor document for client id "foo" short name "MOD02QKM" version id "5" and data center "LAADS" 36 | 37 | Scenario: Generate granule open search descriptor document and see attribution and syndication 38 | Given I am on the open search home page 39 | And I fill in the "granules" open search descriptor form with a client id of "foo" 40 | And I click on "Generate" within the "granules" form 41 | Then I should see a granule open search descriptor document for client id "foo" 42 | And I should see an attribution of "NASA CMR" 43 | And I should see a syndication right of "open" 44 | -------------------------------------------------------------------------------- /config/environments/sit.rb: -------------------------------------------------------------------------------- 1 | EchoOpensearch::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like 20 | # NGINX, varnish or squid. 21 | # config.action_dispatch.rack_cache = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # Compress JavaScripts and CSS. 28 | config.assets.js_compressor = :uglifier 29 | # config.assets.css_compressor = :sass 30 | 31 | # Do not fallback to assets pipeline if a precompiled asset is missed. 32 | config.assets.compile = false 33 | 34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 35 | # yet still be able to expire them through the digest params. 36 | config.assets.digest = true 37 | 38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 39 | 40 | # Specifies the header that your server uses for sending files. 41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 43 | 44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 45 | # config.force_ssl = true 46 | 47 | # Use the lowest log level to ensure availability of diagnostic information 48 | # when problems arise. 49 | config.log_level = :debug 50 | 51 | config.relative_url_root = '/opensearch' 52 | config.graphql_endpoint = ENV['GRAPHQL_ENDPOINT'] 53 | config.cache_store = :redis_cache_store, { url: "redis://#{ENV['REDIS_URL']}:#{ENV['REDIS_PORT']}" } 54 | 55 | # 12-factor 56 | config.assets.initialize_on_precompile = false 57 | 58 | config.eosdis_providers = %w[ 59 | GHRC 60 | OB_DAAC 61 | LPDAAC_ECS 62 | LARC 63 | ASF 64 | NSIDC_ECS 65 | GES_DISC 66 | LARC_ASDC 67 | LAADS 68 | ] 69 | 70 | # config.ceos_agencies = [] 71 | 72 | Rails.logger = Logger.new("#{Rails.root}/log/#{Rails.env}.log") 73 | Rails.logger.formatter = Logger::Formatter.new 74 | 75 | puts 'This is a sit deployment' 76 | end 77 | -------------------------------------------------------------------------------- /app/views/granules/usgslsi.xml.erb: -------------------------------------------------------------------------------- 1 | 2 | 9 | USGS/EROS OpenSearch 10 | USGS/EROS OpenSearch 11 | custserv@usgs.gov 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | USGS/EROS 28 | USGS/EROS Long Term Archive 29 | UTF-8 30 | en-us 31 | UTF-8 32 | open 33 | USGS,EROS,EarthExplorer,OpenSearch 34 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-ui-sliderAccess.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery UI Slider Access 3 | * By: Trent Richardson [http://trentrichardson.com] 4 | * Version 0.2 5 | * Last Modified: 12/02/2011 6 | * 7 | * Copyright 2011 Trent Richardson 8 | * Dual licensed under the MIT and GPL licenses. 9 | * http://trentrichardson.com/Impromptu/GPL-LICENSE.txt 10 | * http://trentrichardson.com/Impromptu/MIT-LICENSE.txt 11 | * 12 | */ 13 | (function($){ 14 | 15 | $.fn.extend({ 16 | sliderAccess: function(options){ 17 | options = options || {}; 18 | options.touchonly = options.touchonly !== undefined? options.touchonly : true; // by default only show it if touch device 19 | 20 | if(options.touchonly === true && !("ontouchend" in document)) 21 | return $(this); 22 | 23 | return $(this).each(function(i,obj){ 24 | var $t = $(this), 25 | o = $.extend({},{ 26 | where: 'after', 27 | step: $t.slider('option','step'), 28 | upIcon: 'ui-icon-plus', 29 | downIcon: 'ui-icon-minus', 30 | text: false, 31 | upText: '+', 32 | downText: '-', 33 | buttonset: true, 34 | buttonsetTag: 'span' 35 | }, options), 36 | $buttons = $('<'+ o.buttonsetTag +' class="ui-slider-access">'+ 37 | ''+ 38 | ''+ 39 | ''); 40 | 41 | $buttons.children('button').each(function(j, jobj){ 42 | var $jt = $(this); 43 | $jt.button({ 44 | text: o.text, 45 | icons: { primary: $jt.data('icon') } 46 | }) 47 | .click(function(e){ 48 | var step = $jt.data('step'), 49 | curr = $t.slider('value'), 50 | newval = curr += step*1, 51 | minval = $t.slider('option','min'), 52 | maxval = $t.slider('option','max'); 53 | 54 | e.preventDefault(); 55 | 56 | if(newval < minval || newval > maxval) 57 | return; 58 | 59 | $t.slider('value', newval); 60 | 61 | $t.slider("option", "slide").call($t, null, { value: newval }); 62 | }); 63 | }); 64 | 65 | // before or after 66 | $t[o.where]($buttons); 67 | 68 | if(o.buttonset){ 69 | $buttons.removeClass('ui-corner-right').removeClass('ui-corner-left').buttonset(); 70 | $buttons.eq(0).addClass('ui-corner-left'); 71 | $buttons.eq(1).addClass('ui-corner-right'); 72 | } 73 | 74 | // adjust the width so we don't break the original layout 75 | var bOuterWidth = $buttons.css({ 76 | marginLeft: (o.where == 'after'? 10:0), 77 | marginRight: (o.where == 'before'? 10:0) 78 | }).outerWidth(true) + 5; 79 | var tOuterWidth = $t.outerWidth(true); 80 | $t.css('display','inline-block').width(tOuterWidth-bOuterWidth); 81 | }); 82 | } 83 | }); 84 | 85 | })(jQuery); 86 | -------------------------------------------------------------------------------- /app/views/shared/_result.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

<%= resource.title %>

4 | 5 | <% unless resource.canononical_link.nil? %> 6 | <%= resource.canononical_link.href %> 7 | <% end %> 8 |
9 | <% unless resource.description.nil? %> 10 | Description: 11 |

<%= resource.description %>

12 | <% end %> 13 | Links: 14 | 19 | Temporal Extent: 20 | 21 | Spatial Extent: <%= render_spatial_extent(resource.bounding_box, resource.polygon, resource.line, resource.point) %> 22 |
23 |

24 | <%= resource.uid.gsub("#{ENV['opensearch_url']}/concept.atom?uid=", '').split('-').last %> 25 | <% if resource_type == 'collection' %> 26 | Short Name: <%= resource.short_name %> 27 | Version ID: <%= resource.version_id %> 28 | <% end %> 29 | Unique ID: <%= resource.uid.gsub("#{ENV['opensearch_url']}/#{resource_type}s.atom?uid=", '') %> 30 |

31 |
32 | <% if resource.has_granules == true %> 33 | <%= link_to :controller => 'granules', :clientId => 'our_html_ui', :keyword => @collection.keyword, :instrument => @collection.instrument, :satellite => @collection.satellite, :shortName => resource.short_name, :versionId => resource.version_id, :dataCenter => resource.data_center, :startTime => @collection.start_time, :endTime => @collection.end_time, :spatial_type => @spatial_type, :boundingBox => @collection.bounding_box, :geometry => @collection.geometry, :placeName => @collection.place_name, :dataset_id => resource.title, :collection_cursor => @cursor, :collection_number_of_results => @number_of_results do %> 34 | 37 | <% end %> 38 | <% elsif resource_type == 'collection' %> 39 | 40 | <% end %> 41 |
42 |
43 |
44 |
-------------------------------------------------------------------------------- /config/environments/uat.rb: -------------------------------------------------------------------------------- 1 | EchoOpensearch::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like 20 | # NGINX, varnish or squid. 21 | # config.action_dispatch.rack_cache = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # Compress JavaScripts and CSS. 28 | config.assets.js_compressor = :uglifier 29 | # config.assets.css_compressor = :sass 30 | 31 | # Do not fallback to assets pipeline if a precompiled asset is missed. 32 | config.assets.compile = false 33 | 34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 35 | # yet still be able to expire them through the digest params. 36 | config.assets.digest = true 37 | 38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 39 | 40 | # Specifies the header that your server uses for sending files. 41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 43 | 44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 45 | # config.force_ssl = true 46 | 47 | # Use the lowest log level to ensure availability of diagnostic information 48 | # when problems arise. 49 | config.log_level = :debug 50 | 51 | config.relative_url_root = '/opensearch' 52 | config.graphql_endpoint = ENV['GRAPHQL_ENDPOINT'] 53 | config.cache_store = :redis_cache_store, { url: "redis://#{ENV['REDIS_URL']}:#{ENV['REDIS_PORT']}" } 54 | 55 | config.eosdis_providers = %w[ 56 | SEDAC 57 | NSIDC_TS1 58 | ORNL_DAAC 59 | PODAAC 60 | ASF 61 | GHRC 62 | LARC_TS1 63 | LARC_ASDC 64 | GES_DISC 65 | CDDIS 66 | NSIDCV0TST 67 | LPDAAC_TS1 68 | USGS_EROS 69 | LPDAAC_TS2 70 | OBPG 71 | LANCEMODIS 72 | LAADS 73 | LANCEAMSR2 74 | OMINRT 75 | ] 76 | 77 | # config.ceos_agencies = [] 78 | 79 | Rails.logger = Logger.new("#{Rails.root}/log/#{Rails.env}.log") 80 | Rails.logger.formatter = Logger::Formatter.new 81 | 82 | puts 'This is a uat deployment' 83 | end 84 | -------------------------------------------------------------------------------- /features/collections/atom/search_validation.feature: -------------------------------------------------------------------------------- 1 | @datasets_search_validation_atom 2 | Feature: Retrieve collections in atom format 3 | In order to obtain collection products 4 | as an open search user 5 | I should be warned of any errors in my search constraints 6 | 7 | Scenario: Search for collection using invalid spatial bounding box 8 | Given I have executed a collection search with the following parameters: 9 | | clientId | boundingBox | keyword | 10 | | foo | foo | OPENSEARCH | 11 | Then I should see a valid error response 12 | And I should see 1 error 13 | And I should see the error "Boundingbox foo is not a valid boundingBox" 14 | 15 | Scenario: Search for collection using invalid start temporal constraints 16 | Given I have executed a collection search with the following parameters: 17 | | clientId | startTime | endTime | keyword | 18 | | foo | foo | 1999-02-01T23:00:00Z | OPENSEARCH | 19 | Then I should see a valid error response 20 | And I should see 1 error 21 | And I should see the error "Starttime foo is not a valid rfc3339 date" 22 | 23 | Scenario: Search for collection using invalid end temporal constraints 24 | Given I have executed a collection search with the following parameters: 25 | | clientId | startTime | endTime | keyword | 26 | | foo | 1999-02-01T23:00:00Z | foo | OPENSEARCH | 27 | Then I should see a valid error response 28 | And I should see 1 error 29 | And I should see the error "Endtime foo is not a valid rfc3339 date" 30 | 31 | Scenario: Search for collection using invalid paging 32 | Given I have executed a collection search with the following parameters: 33 | | clientId | numberOfResults | cursor | keyword | 34 | | foo | foo | 1 | Paging OPENSEARCH | 35 | Then I should see a valid error response 36 | And I should see 1 error 37 | And I should see the error "Numberofresults is not a number" 38 | And I have executed a collection search with the following parameters: 39 | | clientId | numberOfResults | cursor | keyword | 40 | | foo | 2 | foo | Paging OPENSEARCH | 41 | Then I should see a valid error response 42 | And I should see 1 error 43 | And I should see the error "Cursor is not a number" 44 | And I have executed a collection search with the following parameters: 45 | | clientId | numberOfResults | cursor | keyword | 46 | | foo | bar | foo | Paging OPENSEARCH | 47 | Then I should see a valid error response 48 | And I should see 2 errors 49 | And I should see the error "Numberofresults is not a number" 50 | And I should see the error "Cursor is not a number" 51 | 52 | Scenario: Search for collection using invalid constraint 53 | Given I have executed a collection search with the following parameters: 54 | | clientId | foo | 55 | | foo | bar | 56 | Then I should see a valid collection atom response 57 | And I should see 10 results 58 | -------------------------------------------------------------------------------- /spec/controllers/collections_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe CollectionsController do 3 | describe 'GET descriptor_document' do 4 | context 'with valid attributes' do 5 | it 'renders a descriptor document' do 6 | get :descriptor_document, :format => :xml, :params => { :clientId => 'foo' } 7 | expect(response).to render_template("descriptor_document") 8 | end 9 | end 10 | context 'with invalid attributes' do 11 | it 'renders an error' do 12 | get :descriptor_document, :format => :xml, :params => { :clientId => '###' } 13 | expect(response.status).to eq(400) 14 | end 15 | end 16 | context 'with valid and invalid query parameters' do 17 | it 'renders a descriptor document' do 18 | request.env['clientId'] = 'foo' 19 | get :descriptor_document, :format => :xml, :params => { :clientId => 'foo', :invalid_query_parameter => 'invalid_query_parameter_value' } 20 | expect(response.status).to eq(200) 21 | expect(response).to render_template("descriptor_document") 22 | end 23 | end 24 | context 'with invalid attributes' do 25 | it 'renders an error' do 26 | get :descriptor_document, :format => :xml, :params => { :clientId => '###' } 27 | expect(response.status).to eq(400) 28 | end 29 | end 30 | end 31 | 32 | describe 'GET descriptor_document facets' do 33 | context 'with valid attributes' do 34 | it 'renders the facet-enabled descriptor document' do 35 | get :descriptor_document_facets, :format => :xml, :params => { :clientId => 'foo' } 36 | expect(response).to render_template("descriptor_document_facets") 37 | end 38 | end 39 | context 'with invalid attributes' do 40 | it 'renders an error' do 41 | get :descriptor_document_facets, :format => :xml, :params => { :clientId => '###' } 42 | expect(response.status).to eq(400) 43 | end 44 | end 45 | context 'with valid and invalid query parameters' do 46 | it 'renders the facet-enabled descriptor document' do 47 | get :descriptor_document_facets, :format => :xml, :params => { :clientId => 'foo', :invalid_query_parameter => 'invalid_query_parameter_value' } 48 | expect(response.status).to eq(200) 49 | expect(response).to render_template("descriptor_document_facets") 50 | end 51 | end 52 | end 53 | 54 | describe 'GET RestClient error' do 55 | context 'with larger than allowed cursor value' do 56 | it 'is possible to execute an OpenSearch dataset query with a larger than allowed cursor and NOT get an internal server error' do 57 | VCR.use_cassette 'controllers/datasets_cursor_too_large', :record => :once do 58 | get :index, :format => :atom, :params => { :clientId => 'foo', :cursor => '174558594' } 59 | assert_equal '400', response.code 60 | assert_equal 'The paging depth (page_num * page_size) of [1745585940] exceeds the limit of 1000000.', response.body 61 | end 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /app/views/granules/eumetsat.xml.erb: -------------------------------------------------------------------------------- 1 | 2 | 8 | EOPOS Adaptor 9 | EUMETSAT EO Portal Clearinghouse OpenSearch Catalogue Service for Earth Observation Products (EOP) 10 | The EUMETSAT EO Portal Clearinghouse OpenSearch EOP (EOPOS) Catalogue Service is the data cswDiscovery service for EUMETSAT EO Products, based on U-MARF (DataCentre). 11 | ops@eumetsat.int 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | false 30 | con terra GmbH 31 | UTF-8 32 | * 33 | UTF-8 34 | open 35 | CEOS-OS-BP-V1.1/L1 EUMETSAT meteorology satellite weather climate OpenSearch EOP Catalogue Service EOPOS CSW catalogue service 36 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | # IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. 2 | # It is recommended to regenerate this file in the future when you upgrade to a 3 | # newer version of cucumber-rails. Consider adding your own code to a new file 4 | # instead of editing this one. Cucumber will automatically load all features/**/*.rb 5 | # files. 6 | 7 | require 'cucumber/rails' 8 | 9 | require 'simplecov' 10 | SimpleCov.start 'rails' do 11 | add_filter 'features' 12 | end 13 | 14 | # Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In 15 | # order to ease the transition to Capybara we set the default here. If you'd 16 | # prefer to use XPath just remove this line and adjust any selectors in your 17 | # steps to use the XPath syntax. 18 | Capybara.default_selector = :css 19 | 20 | # By default, any exception happening in your Rails application will bubble up 21 | # to Cucumber so that your scenario will fail. This is a different from how 22 | # your application behaves in the production environment, where an error page will 23 | # be rendered instead. 24 | # 25 | # Sometimes we want to override this default behaviour and allow Rails to rescue 26 | # exceptions and display an error page (just like when the app is running in production). 27 | # Typical scenarios where you want to do this is when you test your error pages. 28 | # There are two ways to allow Rails to rescue exceptions: 29 | # 30 | # 1) Tag your scenario (or feature) with @allow-rescue 31 | # 32 | # 2) Set the value below to true. Beware that doing this globally is not 33 | # recommended as it will mask a lot of errors for you! 34 | # 35 | ActionController::Base.allow_rescue = false 36 | 37 | # Remove/comment out the lines below if your app doesn't have a database. 38 | # For some databases (like MongoDB and CouchDB) you may need to use :truncation instead. 39 | #begin 40 | # DatabaseCleaner.strategy = :transaction 41 | #rescue NameError 42 | # raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it." 43 | #end 44 | 45 | # You may also want to configure DatabaseCleaner to use different strategies for certain features and scenarios. 46 | # See the DatabaseCleaner documentation for details. Example: 47 | # 48 | # Before('@no-txn,@selenium,@culerity,@celerity,@javascript') do 49 | # # { :except => [:widgets] } may not do what you expect here 50 | # # as tCucumber::Rails::Database.javascript_strategy overrides 51 | # # this setting. 52 | # DatabaseCleaner.strategy = :truncation 53 | # end 54 | # 55 | # Before('~@no-txn', '~@selenium', '~@culerity', '~@celerity', '~@javascript') do 56 | # DatabaseCleaner.strategy = :transaction 57 | # end 58 | # 59 | 60 | # Possible values are :truncation and :transaction 61 | # The :transaction strategy is faster, but might give you threading problems. 62 | # See https://github.com/cucumber/cucumber-rails/blob/master/features/choose_javascript_database_strategy.feature 63 | Cucumber::Rails::Database.javascript_strategy = :truncation 64 | 65 | host = ENV['TESTHOST'] || 'http://localhost:8080' 66 | # may be non url was given 67 | if not host.include?("//") 68 | host = "https://#{host}" 69 | end 70 | 71 | Capybara.app_host = host 72 | -------------------------------------------------------------------------------- /spec/fixtures/controllers/granules_cursor_too_large.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api.echo.nasa.gov/echo-rest//tokens 6 | body: 7 | encoding: US-ASCII 8 | string: |- 9 | 10 | guest 11 | guest@echo.nasa.gov 12 | foo-opensearch-dev 13 | 0.0.0.0 14 | 15 | headers: 16 | Accept: 17 | - '*/*; q=0.5, application/xml' 18 | Accept-Encoding: 19 | - gzip, deflate 20 | Content-Type: 21 | - application/xml 22 | Content-Length: 23 | - '217' 24 | User-Agent: 25 | - Ruby 26 | response: 27 | status: 28 | code: 201 29 | message: Created 30 | headers: 31 | Server: 32 | - Apache-Coyote/1.1 33 | Location: 34 | - https://api.echo.nasa.gov/echo-rest/tokens/echo-token-here?clientId=unknown 35 | Cache-Control: 36 | - no-cache 37 | X-Ua-Compatible: 38 | - IE=Edge,chrome=1 39 | Set-Cookie: 40 | - _EchoRestRails_session=BAh7BiIPc2Vzc2lvbl9pZCIlZmJiOWRhZGE1NjBiMTgzZjVmYjdhNTYzZDk3ZDNiY2Y%3D--bf3a25c093a037c8475ab46063c08ee69546ac7a; path=/; HttpOnly 41 | X-Runtime: 42 | - '0.115000' 43 | Content-Type: 44 | - application/xml;charset=utf-8 45 | Content-Encoding: 46 | - gzip 47 | Vary: 48 | - Accept-Encoding 49 | Date: 50 | - Wed, 13 May 2015 20:27:55 GMT 51 | Transfer-Encoding: 52 | - chunked 53 | body: 54 | encoding: ASCII-8BIT 55 | string: !binary |- 56 | H4sIAAAAAAAAAE2OTQ6CMBhE95yiYV9asFhJSomKnEDXhNBPbISWUCAeX/w3 57 | s5q8l8mI7Na1aIbBaWtSPwyoj8DUVmnTpP7pWOCNn0lPjPYKRnoICa3kNmH8 58 | kBQcxwktcL7LKWYrynESxWwf8jWNWC7IIj78ycFgqg5kM4EbBfn2B6xbDWYs 59 | F/VsLbY9GAfVUF+wglmQH/0MlbovK6UGcE7S4JnX4j/wBHnfvQPWK3GE3gAA 60 | AA== 61 | http_version: 62 | recorded_at: Wed, 13 May 2015 20:27:56 GMT 63 | - request: 64 | method: get 65 | uri: https://cmr.earthdata.nasa.gov/search/granules.atom?dataset_id=MODIS/Terra%2BAqua%20Leaf%20Area%20Index/FPAR%208-Day%20L4%20Global%201km%20SIN%20Grid%20V005&page_num=174558594&page_size=10 66 | body: 67 | encoding: US-ASCII 68 | string: '' 69 | headers: 70 | Accept: 71 | - '*/*; q=0.5, application/xml' 72 | Accept-Encoding: 73 | - gzip, deflate 74 | Client-Id: 75 | - cmr-open-search 76 | User-Agent: 77 | - Ruby 78 | response: 79 | status: 80 | code: 400 81 | message: Bad Request 82 | headers: 83 | Date: 84 | - Wed, 13 May 2015 20:27:56 GMT 85 | Access-Control-Allow-Origin: 86 | - '*' 87 | Content-Type: 88 | - application/xml;charset=ISO-8859-1 89 | Content-Length: 90 | - '155' 91 | Server: 92 | - Jetty(7.x.y-SNAPSHOT) 93 | body: 94 | encoding: US-ASCII 95 | string: The paging depth (page_num * page_size) of [1745585940] exceeds the limit of 1000000. 96 | http_version: 97 | recorded_at: Wed, 13 May 2015 20:27:56 GMT 98 | recorded_with: VCR 2.9.2 99 | -------------------------------------------------------------------------------- /spec/fixtures/controllers/datasets_cursor_too_large.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api.echo.nasa.gov/echo-rest//tokens 6 | body: 7 | encoding: US-ASCII 8 | string: |- 9 | 10 | guest 11 | guest@echo.nasa.gov 12 | foo-opensearch-dev 13 | 0.0.0.0 14 | 15 | headers: 16 | Accept: 17 | - "*/*; q=0.5, application/xml" 18 | Accept-Encoding: 19 | - gzip, deflate 20 | Content-Type: 21 | - application/xml 22 | Content-Length: 23 | - '217' 24 | User-Agent: 25 | - Ruby 26 | response: 27 | status: 28 | code: 201 29 | message: Created 30 | headers: 31 | Server: 32 | - Apache-Coyote/1.1 33 | Location: 34 | - https://api.echo.nasa.gov/echo-rest/tokens/echo-token-here?clientId=unknown 35 | Cache-Control: 36 | - no-cache 37 | X-Ua-Compatible: 38 | - IE=Edge,chrome=1 39 | Set-Cookie: 40 | - _EchoRestRails_session=BAh7BiIPc2Vzc2lvbl9pZCIlYmE4MWVlOTgzYzdhZjQ4MGI1NmM2MGJkOTQ1YjFiYjk%3D--45f1bb43679f14624532db1603e78cba4bd675d8; 41 | path=/; HttpOnly 42 | X-Runtime: 43 | - '0.194000' 44 | Content-Type: 45 | - application/xml;charset=utf-8 46 | Content-Encoding: 47 | - gzip 48 | Vary: 49 | - Accept-Encoding 50 | Date: 51 | - Thu, 14 May 2015 23:06:18 GMT 52 | Transfer-Encoding: 53 | - chunked 54 | body: 55 | encoding: ASCII-8BIT 56 | string: !binary |- 57 | H4sIAAAAAAAAAE2OywqDMBRE935FyD7GR0sNxIhg8wXtWsTc2lBNJFHp51f7 58 | ZlbDOQzDi/vQowWc19bkOA4jjMC0VmnT5fh8kiTDhQj4ZG9gRIAQ10qkZbxj 59 | ZSpJEicVOUaMEVZljEi538sokZlkB05XcfNnD840A4huBj9x+u0bbHsNZqpX 60 | 9WItsSMYD41rr0TBwumPfoZqPdaNUg68F1H4zGvxHwScvu8+ALqGhMfeAAAA 61 | http_version: 62 | recorded_at: Thu, 14 May 2015 23:06:20 GMT 63 | - request: 64 | method: get 65 | uri: https://cmr.earthdata.nasa.gov/search/collections.atom?include_has_granules=true&include_tags=org.ceos.wgiss.cwic.*,opensearch.granule.osdd,org.geoss.geoss_data-core,gov.nasa.eosdis,int.esa.fedeo&page_num=174558594&page_size=10 66 | body: 67 | encoding: US-ASCII 68 | string: '' 69 | headers: 70 | Accept: 71 | - "*/*; q=0.5, application/xml" 72 | Accept-Encoding: 73 | - gzip, deflate 74 | Client-Id: 75 | - cmr-open-search 76 | User-Agent: 77 | - Ruby 78 | response: 79 | status: 80 | code: 400 81 | message: Bad Request 82 | headers: 83 | Date: 84 | - Tue, 02 Feb 2016 17:41:55 GMT 85 | Content-Type: 86 | - application/xml; charset=ISO-8859-1 87 | Access-Control-Allow-Origin: 88 | - "*" 89 | Content-Length: 90 | - '155' 91 | Server: 92 | - Jetty(9.2.z-SNAPSHOT) 93 | body: 94 | encoding: UTF-8 95 | string: The paging depth 96 | (page_num * page_size) of [1745585940] exceeds the limit of 1000000. 97 | http_version: 98 | recorded_at: Tue, 02 Feb 2016 17:41:55 GMT 99 | recorded_with: VCR 2.9.2 100 | -------------------------------------------------------------------------------- /spec/fixtures/controllers/granules/graphql/invalid.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://graphql.earthdata.nasa.gov/api 6 | body: 7 | encoding: UTF-8 8 | string: '{"query":"query OpenSearchCollection(\n $conceptId: String!\n) {\n collection 9 | (params: {\n conceptId: $conceptId,\n includeTags: \"opensearch.granule.osdd, 10 | org.ceos.wgiss.cwic.granules.provider, org.ceos.wgiss.cwic.granules.native_id\"\n }) 11 | {\n conceptId\n relatedUrls\n spatialExtent\n tags\n temporalExtents\n granules 12 | {\n count\n }\n }\n}\n","variables":{"conceptId":"invalid"}}' 13 | headers: 14 | Accept: 15 | - application/json 16 | Accept-Encoding: 17 | - gzip, deflate 18 | User-Agent: 19 | - rest-client/2.0.2 (darwin21.3.0 x86_64) ruby/2.6.7p197 20 | Content-Type: 21 | - application/json 22 | Client-Id: 23 | - opensearch-test-client 24 | Content-Length: 25 | - '417' 26 | Host: 27 | - graphql.earthdata.nasa.gov 28 | response: 29 | status: 30 | code: 200 31 | message: OK 32 | headers: 33 | Content-Type: 34 | - application/json; charset=utf-8 35 | Content-Length: 36 | - '622' 37 | Connection: 38 | - keep-alive 39 | Server: 40 | - CloudFront 41 | Date: 42 | - Wed, 25 May 2022 12:44:20 GMT 43 | X-Amzn-Remapped-X-Amzn-Requestid: 44 | - d9b864d8-618c-48d3-80c8-3f36c85f7225 45 | X-Amzn-Remapped-Content-Length: 46 | - '622' 47 | X-Amzn-Remapped-Connection: 48 | - keep-alive 49 | X-Amz-Apigw-Id: 50 | - SrqpsHqHoAMFlKA= 51 | Vary: 52 | - Origin 53 | X-Amzn-Remapped-Server: 54 | - Server 55 | Etag: 56 | - W/"26e-nvcXbRUQTGlSIAs8Qjrt8CRWvhk" 57 | X-Powered-By: 58 | - Express 59 | X-Amzn-Remapped-X-Amzn-Remapped-Content-Length: 60 | - '622' 61 | X-Amzn-Trace-Id: 62 | - Root=1-628e24a4-4af5afe008394357378a9ab3;Sampled=0 63 | X-Amzn-Remapped-Date: 64 | - Wed, 25 May 2022 12:44:20 GMT 65 | Access-Control-Allow-Credentials: 66 | - 'true' 67 | X-Request-Id: 68 | - um446Cx2aSKKx1rv2Oc1vEHRt9s0qqivylVq7K_lfYdwHjfgEaquow== 69 | X-Content-Type-Options: 70 | - nosniff 71 | X-Frame-Options: 72 | - SAMEORIGIN 73 | X-Xss-Protection: 74 | - 1; mode=block 75 | Strict-Transport-Security: 76 | - max-age=31536000 77 | X-Amzn-Requestid: 78 | - d8fe2020-0bfa-44d6-bb29-873740d8639a 79 | X-Cache: 80 | - Miss from cloudfront 81 | Via: 82 | - 1.1 92d95cda7eaeed8a1f0f8b1603cfc362.cloudfront.net (CloudFront) 83 | X-Amz-Cf-Pop: 84 | - ORD53-C3 85 | X-Amz-Cf-Id: 86 | - um446Cx2aSKKx1rv2Oc1vEHRt9s0qqivylVq7K_lfYdwHjfgEaquow== 87 | body: 88 | encoding: UTF-8 89 | string: '{"errors":[{"message":"Concept-id [invalid] is not valid.","locations":[{"line":4,"column":3}],"path":["collection"],"extensions":{"code":"CMR_ERROR","exception":{"stacktrace":["Error: 90 | Concept-id [invalid] is not valid."," at dn (/var/task/src/graphql/handler.js:1:331587)"," at 91 | /var/task/src/graphql/handler.js:1:343991"," at Generator.throw ()"," at 92 | Un (/var/task/src/graphql/handler.js:1:338330)"," at s (/var/task/src/graphql/handler.js:1:338571)"," at 93 | runMicrotasks ()"," at processTicksAndRejections (internal/process/task_queues.js:95:5)"]}}}],"data":{"collection":null}} 94 | 95 | ' 96 | recorded_at: Wed, 25 May 2022 12:44:20 GMT 97 | recorded_with: VCR 6.0.0 98 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like 20 | # NGINX, varnish or squid. 21 | # config.action_dispatch.rack_cache = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # Do not fallback to assets pipeline if a precompiled asset is missed. 28 | config.assets.compile = false 29 | 30 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 31 | # yet still be able to expire them through the digest params. 32 | config.assets.digest = true 33 | 34 | config.assets.js_compressor = :uglifier 35 | 36 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 37 | 38 | # Specifies the header that your server uses for sending files. 39 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 40 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 41 | 42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 43 | # config.force_ssl = true 44 | 45 | # Use the lowest log level to ensure availability of diagnostic information 46 | # when problems arise. 47 | config.log_level = :debug 48 | 49 | # Prepend all log lines with the following tags. 50 | # config.log_tags = [ :subdomain, :uuid ] 51 | 52 | # Use a different logger for distributed setups. 53 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 54 | 55 | # Use a different cache store in production. 56 | # config.cache_store = :mem_cache_store 57 | 58 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 59 | # config.action_controller.asset_host = 'http://assets.example.com' 60 | 61 | # Ignore bad email addresses and do not raise email delivery errors. 62 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 63 | # config.action_mailer.raise_delivery_errors = false 64 | 65 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 66 | # the I18n.default_locale when a translation cannot be found). 67 | config.i18n.fallbacks = true 68 | 69 | # Send deprecation notices to registered listeners. 70 | config.active_support.deprecation = :notify 71 | 72 | # Use default logging formatter so that PID and timestamp are not suppressed. 73 | config.log_formatter = ::Logger::Formatter.new 74 | 75 | # Do not dump schema after migrations. 76 | # config.active_record.dump_schema_after_migration = false 77 | 78 | config.relative_url_root = '/opensearch' 79 | config.graphql_endpoint = ENV['GRAPHQL_ENDPOINT'] 80 | config.cache_store = :redis_cache_store, { url: "redis://#{ENV['REDIS_URL']}:#{ENV['REDIS_PORT']}" } 81 | 82 | Rails.logger = Logger.new("#{Rails.root}/log/#{Rails.env}.log") 83 | Rails.logger.formatter = Logger::Formatter.new 84 | 85 | puts 'This is a production deployment' 86 | end 87 | -------------------------------------------------------------------------------- /app/assets/stylesheets/resource.css: -------------------------------------------------------------------------------- 1 | #google_map { 2 | position: relative; 3 | width: 570px; 4 | height: 400px; 5 | border: 1px solid #ccc; 6 | } 7 | 8 | div#google_map { 9 | display: none; 10 | /* margin-left: 186px; */ 11 | margin-left: 163px; 12 | /* width: 716px; */ 13 | width: 563px; 14 | } 15 | 16 | .latlon-container { 17 | margin-right:.5em; 18 | background-color: #fff; 19 | color:#000; 20 | } 21 | 22 | /* The smart UI will have an optional time start picker inside the div below */ 23 | #time_start_picker { 24 | position: relative; 25 | /* z-index: 9999; */ 26 | } 27 | 28 | /* The smart UI will have an optional time end picker inside the div below */ 29 | #time_end_picker { 30 | position: relative; 31 | /* z-index: 9999;*/ 32 | } 33 | 34 | a.toggle-diagnostics { 35 | float: right; 36 | } 37 | 38 | div.clear-no-pad { 39 | clear: both; 40 | } 41 | 42 | section#metrics p { 43 | color: #a2a2a2; 44 | display: inline-block; 45 | margin-right: 20px; 46 | vertical-align: middle; 47 | margin-top: 10px; 48 | } 49 | 50 | a#main_resource_link { 51 | color: white; 52 | } 53 | 54 | a.pagination, a.resource-actions 55 | { 56 | background-color: white; 57 | color: #4a739b; 58 | display: inline-block; 59 | padding-top: 3px; 60 | padding-right: 6px; 61 | padding-bottom: 3px; 62 | padding-left: 6px; 63 | border-top-left-radius: 3px; 64 | border-top-right-radius: 3px; 65 | border-bottom-right-radius: 3px; 66 | border-bottom-left-radius: 3px; 67 | box-shadow: #cccccc; 68 | border-top-width: 1px; 69 | border-right-width: 1px; 70 | border-bottom-width: 1px; 71 | border-left-width: 1px; 72 | border-top-style: solid; 73 | border-right-style: solid; 74 | border-bottom-style: solid; 75 | border-left-style: solid; 76 | border-top-color: #4a739b; 77 | border-right-color: #4a739b; 78 | border-bottom-color: #4a739b; 79 | border-left-color: #4a739b; 80 | border-image-source: none; 81 | border-image-repeat: stretch stretch; 82 | text-shadow: #283a4e; 83 | } 84 | 85 | a.pagination:hover, a.resource-actions:hover { 86 | background-color: #4a739b!important; 87 | color: white; 88 | } 89 | 90 | section#results { 91 | height:40em; 92 | font-weight: 200; 93 | overflow:scroll; 94 | background-color: #F0F0F0; 95 | } 96 | 97 | li { 98 | list-style: none; 99 | } 100 | 101 | h3.resource { 102 | text-align: left; 103 | font-weight: bold; 104 | } 105 | 106 | div.resource-buttons { 107 | padding-right: 2em; 108 | } 109 | 110 | ul.browse-links { 111 | padding-top: 1em; 112 | } 113 | 114 | li.browse-link { 115 | display: inline; 116 | } 117 | 118 | span.label { 119 | font-weight: bold; 120 | } 121 | 122 | li.resource { 123 | padding-left: 1em; 124 | padding-top: 1em; 125 | padding-bottom: 1em; 126 | } 127 | 128 | li.alt { 129 | background-color: #FAF9F9; 130 | } 131 | 132 | ul.results { 133 | margin-left: 0em!important; 134 | } 135 | 136 | li.result { 137 | margin-left: 0em!important; 138 | } 139 | 140 | a.pagination { 141 | /*color: white;*/ 142 | } 143 | 144 | h1, h2, h3 { 145 | font-weight: 200; 146 | margin-bottom: 0.5em; 147 | } 148 | 149 | 150 | #form-wrap { 151 | width:1200px; 152 | margin:0 auto; 153 | } 154 | 155 | 156 | div.facets { 157 | float:left; 158 | width:200px; 159 | overflow: auto; 160 | } 161 | 162 | li.facet { 163 | font-weight: bold; 164 | } 165 | div.search-form { 166 | float:right; 167 | width:1000px; 168 | } 169 | 170 | span.info-heading { 171 | font-weight: bold; 172 | } 173 | 174 | span.label { 175 | font-weight: normal; 176 | } 177 | 178 | span.info-value { 179 | font-weight: normal; 180 | } 181 | 182 | span.uid { 183 | display: none; 184 | } 185 | 186 | div.resource-block { 187 | margin-bottom: 1em; 188 | } 189 | 190 | -------------------------------------------------------------------------------- /spec/fixtures/controllers/granules/graphql/C1220566843-USGS_LTA.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://graphql.earthdata.nasa.gov/api 6 | body: 7 | encoding: UTF-8 8 | string: '{"query":"query OpenSearchCollection(\n $conceptId: String!\n) {\n collection 9 | (params: {\n conceptId: $conceptId,\n includeTags: \"opensearch.granule.osdd, 10 | org.ceos.wgiss.cwic.granules.provider, org.ceos.wgiss.cwic.granules.native_id\"\n }) 11 | {\n conceptId\n relatedUrls\n spatialExtent\n tags\n temporalExtents\n granules 12 | {\n count\n }\n }\n}\n","variables":{"conceptId":"C1220566843-USGS_LTA"}}' 13 | headers: 14 | Accept: 15 | - application/json 16 | Accept-Encoding: 17 | - gzip, deflate 18 | User-Agent: 19 | - rest-client/2.0.2 (darwin21.3.0 x86_64) ruby/2.6.7p197 20 | Content-Type: 21 | - application/json 22 | Client-Id: 23 | - opensearch-test-client 24 | Content-Length: 25 | - '430' 26 | Host: 27 | - graphql.earthdata.nasa.gov 28 | response: 29 | status: 30 | code: 200 31 | message: OK 32 | headers: 33 | Content-Type: 34 | - application/json; charset=utf-8 35 | Transfer-Encoding: 36 | - chunked 37 | Connection: 38 | - keep-alive 39 | Server: 40 | - CloudFront 41 | Vary: 42 | - Accept-Encoding 43 | - Origin 44 | Date: 45 | - Wed, 25 May 2022 12:43:06 GMT 46 | X-Amzn-Remapped-X-Amzn-Requestid: 47 | - 00edbbe9-9752-4af4-bf03-b0eaeffb8606 48 | X-Amzn-Remapped-Content-Length: 49 | - '1073' 50 | X-Amzn-Remapped-Connection: 51 | - keep-alive 52 | X-Amz-Apigw-Id: 53 | - SrqeFEiNIAMFWdQ= 54 | X-Amzn-Remapped-Server: 55 | - Server 56 | Etag: 57 | - W/"431-EEU6H2JgS/YEYVJA6MMV94MGU0A" 58 | X-Powered-By: 59 | - Express 60 | X-Amzn-Remapped-X-Amzn-Remapped-Content-Length: 61 | - '1073' 62 | X-Amzn-Trace-Id: 63 | - Root=1-628e2459-172b69e61ae597147e9f7d47;Sampled=0 64 | X-Amzn-Remapped-Date: 65 | - Wed, 25 May 2022 12:43:06 GMT 66 | Access-Control-Allow-Credentials: 67 | - 'true' 68 | X-Request-Id: 69 | - CZCEaZwoddeYcthdrwGPSNetC2yMhZ07mmYjHAOXIVtdtWDSXLCM5w== 70 | X-Content-Type-Options: 71 | - nosniff 72 | X-Frame-Options: 73 | - SAMEORIGIN 74 | X-Xss-Protection: 75 | - 1; mode=block 76 | Strict-Transport-Security: 77 | - max-age=31536000 78 | X-Amzn-Requestid: 79 | - fad65828-0a45-416b-b155-bc700da363b5 80 | Content-Encoding: 81 | - gzip 82 | X-Cache: 83 | - Miss from cloudfront 84 | Via: 85 | - 1.1 8ef3398f0a11d6fa61753f1c21c6c9d2.cloudfront.net (CloudFront) 86 | X-Amz-Cf-Pop: 87 | - ORD53-C3 88 | X-Amz-Cf-Id: 89 | - CZCEaZwoddeYcthdrwGPSNetC2yMhZ07mmYjHAOXIVtdtWDSXLCM5w== 90 | body: 91 | encoding: ASCII-8BIT 92 | string: !binary |- 93 | H4sIAAAAAAAAA4RTXW/aMBR936+I/NRK1Am0Yy3SNGVpSpkYoCS0UqcKmeQuWHPsyHagXcV/3zUJax86DeXF9/Pccw4vpGCWkdELyZUQkFuuZPuSOdR2UpARifqDQfBxOLy8OD9bpuN0Nc1C0iMaBLNQLLUwZPTjhRRgcs3rdgKJVFVxawE8Jb2Yabvx5msDestcgZdiqxDcgvFOoniennr3Sv/isvTGWjW1a4qY4GvdljNZeHf4Ltrnyf04ujv1MjDWS90QingaLSIlLUibPddwgHC8aJlMscC24UUy/xZHmXc7/x57i3Act72Y2Vhbj3w/Z2LLRK20ZYLmoAxVuvR3sPbLBjf6G1UB2T/2iKkRDhPxk1vqWOsCkdqCZiV0OG7nyeRhPstCB2KjNP+NMJlI2+JrVTF+4LwEHGz1c8u/0gWXyFL6bCxU7pwwyeJ0Es5wylo1EtNlgvcxWQpoJdghvK9dKvo7gYz69GrQIxIv2ryXvvhIh+c9Auz97iG9uMJjVfOP7j69HO4f9/seKTWTjYDusgRqDQapYZ0nZvNVugizCRKBxZaVxp2qapAGmM43tOunyhSFS7XePOhinDCVpuCs5OJUMsNoqbb+6wC/G2D8oxmVXhUqbypEQZ8q8eXV5dHR4Z/f9TcCRNVb+XclN4bmO54fERpaa7XlBeg3MF3vNJ38txVp41tY8bcnRuH0LpyuJrg8jJJwubpJwlkUE8cqyo9mPPqsVRpkYUK7aPm9Eawko59MGMC/JfoBrlGZjFedLdZQcilRtmMYF/avPg3Ogsuz/jALgtHho0EQPKCxnbWPYFsvNs7ewR5/H/4AAAD//wMAfxzaYTEEAAA= 94 | recorded_at: Wed, 25 May 2022 12:43:06 GMT 95 | recorded_with: VCR 6.0.0 96 | --------------------------------------------------------------------------------