Skip to content

Commit

Permalink
Implements the OAI-PMH protocol at the '/oai' endpoint.
Browse files Browse the repository at this point in the history
The endpoint supports both 'GET' and 'POST' requests according to the specification.
  • Loading branch information
bmquinn committed Dec 7, 2022
1 parent e0374ce commit 7a0b443
Show file tree
Hide file tree
Showing 14 changed files with 2,056 additions and 6 deletions.
35 changes: 33 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 42 additions & 2 deletions src/api/opensearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ function initRequest(path) {
});
}

async function search(targets, body) {
async function search(targets, body, optionsQuery = {}) {
const endpoint = elasticsearchEndpoint();

const request = new HttpRequest({
Expand All @@ -74,9 +74,49 @@ async function search(targets, body) {
},
body: body,
path: `/${targets}/_search`,
query: optionsQuery,
});

return await awsFetch(request);
}

module.exports = { getCollection, getFileSet, getSharedLink, getWork, search };
async function scroll(scrollId) {
const endpoint = elasticsearchEndpoint();

const request = new HttpRequest({
method: "POST",
hostname: endpoint,
headers: {
Host: endpoint,
"Content-Type": "application/json",
},
body: JSON.stringify({ scroll: "2m" }),
path: `_search/scroll/${scrollId}`,
});
return await awsFetch(request);
}

async function deleteScroll(scrollId) {
const endpoint = elasticsearchEndpoint();

const request = new HttpRequest({
method: "DELETE",
hostname: endpoint,
headers: {
Host: endpoint,
"Content-Type": "application/json",
},
path: `_search/scroll/${scrollId}`,
});
return await awsFetch(request);
}

module.exports = {
getCollection,
getFileSet,
getSharedLink,
getWork,
search,
scroll,
deleteScroll,
};
63 changes: 63 additions & 0 deletions src/handlers/oai.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const { processRequest } = require("./middleware");
const { baseUrl } = require("../helpers");
const {
getRecord,
identify,
listIdentifiers,
listMetadataFormats,
listRecords,
} = require("./oai/verbs");
const { invalidOaiRequest } = require("./oai/xml-transformer");

const allowedVerbs = [
"GetRecord",
"Identify",
"ListIdentifiers",
"ListMetadataFormats",
"ListRecords",
"ListSets",
];

/**
* A function to support the OAI-PMH harvesting specfication
*/
exports.handler = async (event) => {
event = processRequest(event);
const url = `${baseUrl(event)}oai`;
let verb, identifier, metadataPrefix, resumptionToken;
if (event.requestContext.http.method === "GET") {
verb = event.queryStringParameters?.verb;
identifier = event.queryStringParameters?.identifier;
metadataPrefix = event.queryStringParameters?.metadataPrefix;
resumptionToken = event.queryStringParameters?.resumptionToken;
} else {
const body = new URLSearchParams(event.body);
verb = body.get("verb");
identifier = body.get("identifier");
metadataPrefix = body.get("metadataPrefix");
resumptionToken = body.get("resumptionToken");
}

if (!verb) return invalidOaiRequest("badArgument", "Missing required verb");

switch (verb) {
case "GetRecord":
return await getRecord(url, identifier, metadataPrefix);
case "Identify":
return await identify(url);
case "ListIdentifiers":
return await listIdentifiers(url, event, metadataPrefix, resumptionToken);
case "ListMetadataFormats":
return await listMetadataFormats(url);
case "ListRecords":
return await listRecords(url, event, metadataPrefix, resumptionToken);
case "ListSets":
return invalidOaiRequest(
"noSetHierarchy",
"This repository does not support Sets",
401
);
default:
return invalidOaiRequest("badVerb", "Illegal OAI verb");
}
};
56 changes: 56 additions & 0 deletions src/handlers/oai/search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const { search } = require("../../api/opensearch");
const {
extractRequestedModels,
modelsToTargets,
} = require("../../api/request/models");
const fs = require("fs");

async function earliestRecordCreateDate() {
const body = {
size: 1,
_source: "create_date",
query: {
bool: {
must: [
{ term: { api_model: "Work" } },
{ term: { published: true } },
{ term: { visibility: "Public" } },
],
},
},
sort: [{ create_date: "desc" }],
};
const esResponse = await search(
modelsToTargets(extractRequestedModels()),
JSON.stringify(body)
);
const responseBody = JSON.parse(esResponse.body);
return responseBody?.hits?.hits[0]?._source?.create_date;
}

async function oaiSearch() {
const body = {
size: 5000,
query: {
bool: {
must: [
{ term: { api_model: "Work" } },
{ term: { published: true } },
{ term: { visibility: "Public" } },
],
},
},
sort: [{ create_date: "desc" }],
};
const esResponse = await search(
modelsToTargets(extractRequestedModels()),
JSON.stringify(body),
{ scroll: "2m" }
);
return {
...esResponse,
expiration: new Date(new Date().getTime() + 2 * 60000).toISOString(),
};
}

module.exports = { earliestRecordCreateDate, oaiSearch };
Loading

0 comments on commit 7a0b443

Please sign in to comment.