Skip to content

Commit

Permalink
Add a model for querying CrossRef
Browse files Browse the repository at this point in the history
Issue #2541
  • Loading branch information
robyngit committed Oct 14, 2024
1 parent 81210d6 commit b5359d2
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 0 deletions.
114 changes: 114 additions & 0 deletions src/js/models/CrossRefModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
define(["backbone"], (Backbone) => {
const CACHE_PREFIX = "crossref_";
/**
* @class CrossRef
* @classdesc Handles querying CrossRef API for metadata about a DOI.
* @classcategory Models
* @augments Backbone.Model
* @constructs
* @augments Backbone.Model
* @since 0.0.0
*/
const CrossRef = Backbone.Model.extend(
/** @lends CrossRef.prototype */
{
/** @inheritdoc */
type: "CrossRef",

/**
* Defaults for the CrossRef model.
* @type {object}
* @property {string} baseURL - The base URL for the CrossRef API.
* @property {string} email - The email address to use for "polite"
* requests. See https://github.com/CrossRef/rest-api-doc#good-manners--more-reliable-service).
*/
defaults() {
return {
baseURL:
MetacatUI.appModel.get("crossRefAPI") ||
"https://api.crossref.org/works/",
email: MetacatUI.appModel.get("emailContact") || "",
};
},

/** @inheritdoc */
url() {
let doi = this.get("doi");
if (!doi) return null;
// Make sure the DOI is formatted correctly
doi = MetacatUI.appModel.removeAllDOIPrefixes(doi);
this.set("doi", doi);
const doiStr = encodeURIComponent(doi);
const email = this.get("email");
const emailStr = email ? `?mailto:${email}` : "";
const baseURL = this.get("baseURL");
const url = `${baseURL}${doiStr}${emailStr}`;
return url;
},

/** @inheritdoc */
fetch() {
// first check if there's a cached response
const doi = this.get("doi");
const cachedResponse = this.getCachedResponse(doi);
if (cachedResponse) {
this.set(cachedResponse);
return;
}

const url = this.url();
if (!url) return;
const model = this;
// Make the request using native fetch
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
})
.then((responseJSON) => {
const parsedData = responseJSON.message;
model.cacheResponse(doi, parsedData);
model.set(parsedData);
model.trigger("sync");
})
.catch((error) => {
model.trigger("error", error);
model.set("error", "fetchError");
model.set("errorMessage", error.message);
});
},

/**
* Cache the response from the CrossRef API
* @param {string} doi The DOI for the response
* @param {object} response The response from the CrossRef API
*/
cacheResponse(doi, response) {
localStorage.setItem(`${CACHE_PREFIX}${doi}`, JSON.stringify(response));
},

/**
* Get the cached response for a DOI
* @param {string} doi The DOI to get the cached response for
* @returns {object|null} The cached response or null if there is no cached response
*/
getCachedResponse(doi) {
const cachedResponse = localStorage.getItem(`${CACHE_PREFIX}${doi}`);
if (!cachedResponse) return null;
return JSON.parse(cachedResponse);
},

/** Clear the cache of CrossRef responses */
clearCache() {
const keysToRemove = Object.keys(localStorage).filter((key) =>
key.startsWith(CACHE_PREFIX),
);
keysToRemove.forEach((key) => localStorage.removeItem(key));
},
},
);

return CrossRef;
});
1 change: 1 addition & 0 deletions test/config/tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"./js/specs/unit/models/filters/Filter.spec.js",
"./js/specs/unit/models/filters/NumericFilter.spec.js",
"./js/specs/unit/models/CitationModel.spec.js",
"./js/specs/unit/models/CrossRefModel.spec.js",
"./js/specs/unit/collections/ProjectList.spec.js",
"./js/specs/unit/collections/DataPackage.spec.js",
"./js/specs/unit/models/project/Project.spec.js",
Expand Down
37 changes: 37 additions & 0 deletions test/js/specs/unit/models/CrossRefModel.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use strict";

define(["/test/js/specs/shared/clean-state.js", "models/CrossRefModel"], (
cleanState,
CrossRef,
) => {
const should = chai.should();
const expect = chai.expect;

describe("CrossRef Test Suite", () => {
const state = cleanState(() => {
// Example DOI from:

// Jerrentrup, A., Mueller, T., Glowalla, U., Herder, M., Henrichs, N.,
// Neubauer, A., & Schaefer, J. R. (2018). Teaching medicine with the
// help of “Dr. House.” PLoS ONE, 13(3), Article e0193972.
// https://doi.org/10.1371/journal.pone.0193972
const crossRef = new CrossRef({
doi: "https://doi.org/10.1371/journal.pone.0193972",
});
return { crossRef };
}, beforeEach);

it("creates a CrossRef instance", () => {
state.crossRef.should.be.instanceof(CrossRef);
});

it("forms valid fetch URLs", () => {
const url = state.crossRef.url();

url.should.be.a("string");
url.should.include("https://api.crossref.org/works/");
url.should.include("10.1371%2Fjournal.pone.0193972");
url.should.include("?mailto:[email protected]");
});
});
});

0 comments on commit b5359d2

Please sign in to comment.