diff --git a/app/graphql/resolvers/base.rb b/app/graphql/resolvers/base.rb new file mode 100644 index 000000000..4e4d22c77 --- /dev/null +++ b/app/graphql/resolvers/base.rb @@ -0,0 +1,5 @@ +module Resolvers + class Base < GraphQL::Schema::Resolver + + end +end diff --git a/app/graphql/resolvers/content_url.rb b/app/graphql/resolvers/content_url.rb new file mode 100644 index 000000000..b60f72ab0 --- /dev/null +++ b/app/graphql/resolvers/content_url.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class ContentUrl < Resolvers::Base + type Url, null: false + + def resolve + # Use Unpaywall API if DOI is from Crossref + # This will be a future feature in the DataCite metadata schema + return nil if object.agency != "crossref" + + url = "https://api.unpaywall.org/v2/#{object.doi}?email=info@datacite.org" + response = Maremma.get(url) + return nil if response.status != 200 + + response.body.dig("data", "best_oa_location", "url") + end +end diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 010a93f11..4bf13df3f 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -135,6 +135,11 @@ type Audiovisual implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -426,6 +431,11 @@ type Book implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -674,6 +684,11 @@ type BookChapter implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -1008,6 +1023,11 @@ type Collection implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -1298,6 +1318,11 @@ type ConferencePaper implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -1874,6 +1899,11 @@ type DataManagementPlan implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -2166,6 +2196,11 @@ type DataPaper implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -2457,6 +2492,11 @@ type Dataset implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -2859,6 +2899,11 @@ type Dissertation implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -3154,6 +3199,11 @@ interface DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -3402,6 +3452,11 @@ type Event implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -3678,6 +3733,11 @@ type EventData implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -4246,6 +4306,11 @@ type Image implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -4537,6 +4602,11 @@ type Instrument implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -4827,6 +4897,11 @@ type InteractiveResource implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -5143,6 +5218,11 @@ type JournalArticle implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -5680,6 +5760,11 @@ type Model implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -6096,6 +6181,11 @@ type Other implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -6412,6 +6502,11 @@ type PeerReview implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -6834,6 +6929,11 @@ type PhysicalObject implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -7178,6 +7278,11 @@ type Preprint implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -7470,6 +7575,11 @@ type Publication implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -8206,6 +8316,11 @@ type Service implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -8499,6 +8614,11 @@ type Software implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -8822,6 +8942,11 @@ type Sound implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -9296,6 +9421,11 @@ type Work implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. @@ -9589,6 +9719,11 @@ type Workflow implements DoiItem { """ container: Container + """ + Url to download the content directly, if available + """ + contentUrl: Url! + """ The institution or person responsible for collecting, managing, distributing, or otherwise contributing to the development of the resource. diff --git a/app/graphql/types/doi_item.rb b/app/graphql/types/doi_item.rb index 7fc33e837..e0ad4d9ef 100644 --- a/app/graphql/types/doi_item.rb +++ b/app/graphql/types/doi_item.rb @@ -52,6 +52,7 @@ module DoiItem field :geolocations, [GeolocationType], null: true, hash_key: "geo_locations", description: "Spatial region or named place where the data was gathered or about which the data is focused." field :funding_references, [FundingType], null: true, description: "Information about financial support (funding) for the resource being registered" field :url, Url, null: true, description: "The URL registered for the resource" + field :content_url,resolver: ContentUrl, null: true, description: "Url to download the content directly, if available" field :repository, RepositoryType, null: true, hash_key: "client", description: "The repository account managing this resource" field :member, MemberType, null: true, hash_key: "provider", description: "The member account managing this resource" field :registration_agency, RegistrationAgencyType, hash_key: "agency", null: true, description: "The DOI registration agency for the resource" diff --git a/config/application.rb b/config/application.rb index a4280217b..e56928354 100644 --- a/config/application.rb +++ b/config/application.rb @@ -70,6 +70,7 @@ class Application < Rails::Application config.paths.add Rails.root.join('app', 'graphql', 'types').to_s, eager_load: true config.paths.add Rails.root.join('app', 'graphql', 'mutations').to_s, eager_load: true config.paths.add Rails.root.join('app', 'graphql', 'connections').to_s, eager_load: true + config.paths.add Rails.root.join('app', 'graphql', 'resolvers').to_s, eager_load: true # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers diff --git a/spec/fixtures/vcr_cassettes/WorkType/find_work_crossref/returns_work.yml b/spec/fixtures/vcr_cassettes/WorkType/find_work_crossref/returns_work.yml new file mode 100644 index 000000000..7c207d7ee --- /dev/null +++ b/spec/fixtures/vcr_cassettes/WorkType/find_work_crossref/returns_work.yml @@ -0,0 +1,176 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.unpaywall.org/v2/10.1038/NATURE12373?email=info@datacite.org + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Mozilla/5.0 (compatible; Maremma/4.7.2; mailto:info@datacite.org) + Accept: + - text/html,application/json,application/xml;q=0.9, text/plain;q=0.8,image/png,*/*;q=0.5 + response: + status: + code: 200 + message: OK + headers: + Connection: + - keep-alive + Server: + - gunicorn/19.9.0 + Date: + - Mon, 31 Aug 2020 07:09:00 GMT + Content-Type: + - application/json + Content-Length: + - '4437' + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - POST, GET, OPTIONS, PUT, DELETE, PATCH + Access-Control-Allow-Headers: + - origin, content-type, accept, x-requested-with + Via: + - 1.1 vegur + body: + encoding: ASCII-8BIT + string: |- + { + "best_oa_location": { + "endpoint_id": "8c9d8ba370a84253deb", + "evidence": "oa repository (via OAI-PMH doi match)", + "host_type": "repository", + "is_best": true, + "license": null, + "pmh_id": "oai:dash.harvard.edu:1/12285462", + "repository_institution": "Harvard University - Digital Access to Scholarship at Harvard (DASH)", + "updated": "2020-08-13T02:05:07.139585", + "url": "https://dash.harvard.edu/bitstream/1/12285462/1/Nanometer-Scale%20Thermometry.pdf", + "url_for_landing_page": "http://nrs.harvard.edu/urn-3:HUL.InstRepos:12285462", + "url_for_pdf": "https://dash.harvard.edu/bitstream/1/12285462/1/Nanometer-Scale%20Thermometry.pdf", + "version": "publishedVersion" + }, + "data_standard": 2, + "doi": "10.1038/nature12373", + "doi_url": "https://doi.org/10.1038/nature12373", + "genre": "journal-article", + "has_repository_copy": true, + "is_oa": true, + "is_paratext": false, + "journal_is_in_doaj": false, + "journal_is_oa": false, + "journal_issn_l": "0028-0836", + "journal_issns": "0028-0836,1476-4687", + "journal_name": "Nature", + "oa_locations": [ + { + "endpoint_id": "8c9d8ba370a84253deb", + "evidence": "oa repository (via OAI-PMH doi match)", + "host_type": "repository", + "is_best": true, + "license": null, + "pmh_id": "oai:dash.harvard.edu:1/12285462", + "repository_institution": "Harvard University - Digital Access to Scholarship at Harvard (DASH)", + "updated": "2020-08-13T02:05:07.139585", + "url": "https://dash.harvard.edu/bitstream/1/12285462/1/Nanometer-Scale%20Thermometry.pdf", + "url_for_landing_page": "http://nrs.harvard.edu/urn-3:HUL.InstRepos:12285462", + "url_for_pdf": "https://dash.harvard.edu/bitstream/1/12285462/1/Nanometer-Scale%20Thermometry.pdf", + "version": "publishedVersion" + }, + { + "endpoint_id": "pubmedcentral.nih.gov", + "evidence": "oa repository (via OAI-PMH doi match)", + "host_type": "repository", + "is_best": false, + "license": null, + "pmh_id": "oai:pubmedcentral.nih.gov:4221854", + "repository_institution": "pubmedcentral.nih.gov", + "updated": "2017-10-21T12:34:56.074727", + "url": "http://europepmc.org/articles/pmc4221854?pdf=render", + "url_for_landing_page": "http://europepmc.org/articles/pmc4221854", + "url_for_pdf": "http://europepmc.org/articles/pmc4221854?pdf=render", + "version": "acceptedVersion" + }, + { + "endpoint_id": null, + "evidence": "oa repository (via pmcid lookup)", + "host_type": "repository", + "is_best": false, + "license": null, + "pmh_id": null, + "repository_institution": null, + "updated": "2020-08-31T07:09:00.270481", + "url": "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4221854", + "url_for_landing_page": "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4221854", + "url_for_pdf": null, + "version": "acceptedVersion" + }, + { + "endpoint_id": "arXiv.org", + "evidence": "oa repository (via OAI-PMH doi match)", + "host_type": "repository", + "is_best": false, + "license": null, + "pmh_id": "oai:arXiv.org:1304.1068", + "repository_institution": "arXiv.org", + "updated": "2017-10-22T03:05:35.844774", + "url": "http://arxiv.org/pdf/1304.1068", + "url_for_landing_page": "http://arxiv.org/abs/1304.1068", + "url_for_pdf": "http://arxiv.org/pdf/1304.1068", + "version": "submittedVersion" + } + ], + "oa_status": "green", + "published_date": "2013-07-31", + "publisher": "Springer Science and Business Media LLC", + "title": "Nanometre-scale thermometry in a living cell", + "updated": "2020-02-22T03:36:14.193537", + "year": 2013, + "z_authors": [ + { + "family": "Kucsko", + "given": "G.", + "sequence": "first" + }, + { + "family": "Maurer", + "given": "P. C.", + "sequence": "additional" + }, + { + "family": "Yao", + "given": "N. Y.", + "sequence": "additional" + }, + { + "family": "Kubo", + "given": "M.", + "sequence": "additional" + }, + { + "family": "Noh", + "given": "H. J.", + "sequence": "additional" + }, + { + "family": "Lo", + "given": "P. K.", + "sequence": "additional" + }, + { + "family": "Park", + "given": "H.", + "sequence": "additional" + }, + { + "family": "Lukin", + "given": "M. D.", + "sequence": "additional" + } + ] + } + http_version: null + recorded_at: Mon, 31 Aug 2020 07:09:00 GMT +recorded_with: VCR 5.1.0 diff --git a/spec/graphql/types/work_type_spec.rb b/spec/graphql/types/work_type_spec.rb index 8e536fdb8..7c153be60 100644 --- a/spec/graphql/types/work_type_spec.rb +++ b/spec/graphql/types/work_type_spec.rb @@ -78,6 +78,38 @@ end end + describe "find work crossref", elasticsearch: true, vcr: true do + let!(:work) { create(:doi, doi: "10.1038/nature12373", agency: "crossref", aasm_state: "findable", titles: [ + { "title" => "Nanometre-scale thermometry in a living cell" }]) } + + before do + Doi.import + sleep 2 + end + + let(:query) do + %(query { + work(id: "https://doi.org/#{work.doi}") { + id + titles { + title + } + url + contentUrl + } + }) + end + + it "returns work" do + response = LupoSchema.execute(query).as_json + + expect(response.dig("data", "work", "id")).to eq("https://handle.test.datacite.org/#{work.doi.downcase}") + expect(response.dig("data", "work", "titles")).to eq([{"title"=>"Nanometre-scale thermometry in a living cell"}]) + expect(response.dig("data", "work", "url")).to eq(work.url) + expect(response.dig("data", "work", "contentUrl")).to eq("https://dash.harvard.edu/bitstream/1/12285462/1/Nanometer-Scale%20Thermometry.pdf") + end + end + describe "find work not found", elasticsearch: true do let(:query) do %(query {