Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ on:
- main
paths:
- ".github/workflows/deploy.yml"
- "api/**"
- "av-download/**"
- "node/**"
- "chat/**"
- "template.yaml"
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/next_version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
- uses: actions/checkout@v2
with:
ref: deploy/staging
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Bump Version
id: increment
run: |
Expand Down
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,12 @@ BUMP ?= ""
version:
@if [[ -n "$(BUMP)" ]]; then \
for pkg in api api/dependencies api/src av-download/lambdas; do \
(cd $$pkg && npm version $(BUMP)) >/dev/null 2>&1; \
echo -n "Bumping version in $$pkg: " >&2 ; \
(cd $$pkg && npm version $(BUMP)) >&2; \
done; \
for pkg in chat docs; do \
(cd $$pkg && uv version --bump $(BUMP)) >/dev/null 2>&1; \
echo "Bumping version in $$pkg: " >&2 ; \
(cd $$pkg && uv version --bump $(BUMP)) >&2; \
done; \
fi; \
node -e 'console.log(require("./api/package.json").version)'
4 changes: 2 additions & 2 deletions api/dependencies/package-lock.json

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

2 changes: 1 addition & 1 deletion api/dependencies/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dc-api-dependencies",
"version": "2.8.0",
"version": "2.8.1",
"description": "NUL Digital Collections API Dependencies",
"repository": "https://github.com/nulib/dc-api-v2",
"author": "nulib",
Expand Down
4 changes: 2 additions & 2 deletions api/package-lock.json

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

2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dc-api-build",
"version": "2.8.0",
"version": "2.8.1",
"description": "NUL Digital Collections API Build Environment",
"repository": "https://github.com/nulib/dc-api-v2",
"author": "nulib",
Expand Down
6 changes: 3 additions & 3 deletions api/src/handlers/oai.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ function invalidDateParameters(verb, dates) {
// OAI-PMH spec allows three date formats:
// 1. YYYY-MM-DD (date only)
// 2. YYYY-MM-DDThh:mm:ssZ (no fractional seconds)
// 3. YYYY-MM-DDThh:mm:ss.fZ to YYYY-MM-DDThh:mm:ss.ffffffZ (1-6 fractional seconds)
// 3. YYYY-MM-DDThh:mm:ssZ (seconds granularity, no fractional seconds)
const dateOnlyRegex = /^\d{4}-\d{2}-\d{2}$/;
const dateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,6})?Z$/;
const dateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
let invalidDates = [];

for (const [dateParameter, dateValue] of Object.entries(dates)) {
Expand Down Expand Up @@ -65,7 +65,7 @@ exports.handler = wrap(async (event) => {
if (invalidDateParameters(verb, dates).length > 0)
return invalidOaiRequest(
"badArgument",
"Invalid date -- make sure that 'from' or 'until' parameters are formatted as: 'YYYY-MM-DD' or 'YYYY-MM-DDThh:mm:ssZ' (with optional fractional seconds)"
"Invalid date -- make sure that 'from' or 'until' parameters are formatted as: 'YYYY-MM-DD' or 'YYYY-MM-DDThh:mm:ssZ'"
);
if (!verb) return invalidOaiRequest("badArgument", "Missing required verb");

Expand Down
27 changes: 27 additions & 0 deletions api/src/handlers/oai/date-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
function formatOaiDate(value) {
if (!value) return value;

if (typeof value === "string") {
// Preserve day-level granularity values as-is.
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
}

const date =
value instanceof Date
? value
: typeof value === "string"
? new Date(value)
: null;

if (date && !Number.isNaN(date.getTime())) {
return date.toISOString().replace(/\.\d+Z$/, "Z");
}

if (typeof value === "string") {
return value.replace(/\.\d+(?=Z$)/, "");
}

return value;
}

module.exports = { formatOaiDate };
21 changes: 11 additions & 10 deletions api/src/handlers/oai/verbs.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { invalidOaiRequest, output } = require("../oai/xml-transformer");
const { earliestRecord, oaiSearch, oaiSets } = require("../oai/search");
const { deleteScroll, getWork, scroll } = require("../../api/opensearch");
const { formatOaiDate } = require("./date-utils");

const fieldMapper = {
contributor: "dc:contributor",
Expand Down Expand Up @@ -29,7 +30,7 @@ const oaiAttributes = {
function header(work) {
let fields = {
identifier: work.id,
datestamp: work.modified_date,
datestamp: formatOaiDate(work.modified_date),
};

if (work?.collection && Object.keys(work.collection).length > 0) {
Expand Down Expand Up @@ -81,7 +82,7 @@ const getRecord = async (url, id) => {
const document = {
"OAI-PMH": {
_attributes: oaiAttributes,
responseDate: new Date().toISOString(),
responseDate: formatOaiDate(new Date()),
request: {
_attributes: {
verb: "GetRecord",
Expand All @@ -108,7 +109,7 @@ const identify = async (url) => {
const obj = {
"OAI-PMH": {
_attributes: oaiAttributes,
responseDate: new Date().toISOString(),
responseDate: formatOaiDate(new Date()),
request: {
_attributes: {
verb: "Identify",
Expand All @@ -120,7 +121,7 @@ const identify = async (url) => {
baseURL: url,
protocolVersion: "2.0",
adminEmail: "repository@northwestern.edu",
earliestDatestamp: earliestDatestamp,
earliestDatestamp: formatOaiDate(earliestDatestamp),
deletedRecord: "no",
granularity: "YYYY-MM-DDThh:mm:ssZ",
},
Expand Down Expand Up @@ -161,14 +162,14 @@ const listIdentifiers = async (
const headers = hits.map((hit) => header(hit._source));
resumptionTokenElement = {
_attributes: {
expirationDate: response.expiration,
expirationDate: formatOaiDate(response.expiration),
},
_text: scrollId,
};
const obj = {
"OAI-PMH": {
_attributes: oaiAttributes,
responseDate: new Date().toISOString(),
responseDate: formatOaiDate(new Date()),
request: {
_attributes: {
verb: "ListIdentifiers",
Expand Down Expand Up @@ -205,7 +206,7 @@ const listMetadataFormats = (url) => {
const obj = {
"OAI-PMH": {
_attributes: oaiAttributes,
responseDate: new Date().toISOString(),
responseDate: formatOaiDate(new Date()),
request: {
_attributes: {
verb: "ListMetadataFormats",
Expand Down Expand Up @@ -256,14 +257,14 @@ const listRecords = async (
records = hits.map((hit) => transform(hit._source));
resumptionTokenElement = {
_attributes: {
expirationDate: response.expiration,
expirationDate: formatOaiDate(response.expiration),
},
_text: scrollId,
};
const obj = {
"OAI-PMH": {
_attributes: oaiAttributes,
responseDate: new Date().toISOString(),
responseDate: formatOaiDate(new Date()),
request: {
_attributes: {
verb: "ListRecords",
Expand Down Expand Up @@ -312,7 +313,7 @@ const listSets = async (url) => {
const obj = {
"OAI-PMH": {
_attributes: oaiAttributes,
responseDate: new Date().toISOString(),
responseDate: formatOaiDate(new Date()),
request: {
_attributes: {
verb: "ListSets",
Expand Down
3 changes: 2 additions & 1 deletion api/src/handlers/oai/xml-transformer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const convert = require("xml-js");
const { formatOaiDate } = require("./date-utils");

const json2xmlOptions = { compact: true, ignoreComment: true, spaces: 4 };

Expand All @@ -15,7 +16,7 @@ const invalidOaiRequest = (oaiCode, message, statusCode = 400) => {
"xsi:schemaLocation":
"http://www.openarchives.org/OAI/2.0/\nhttp://www.openarchives.org/OAI/2.0/OAI_PMH.xsd",
},
responseDate: new Date().toISOString(),
responseDate: formatOaiDate(new Date()),
error: {
_attributes: {
code: oaiCode,
Expand Down
4 changes: 2 additions & 2 deletions api/src/package-lock.json

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

2 changes: 1 addition & 1 deletion api/src/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dc-api",
"version": "2.8.0",
"version": "2.8.1",
"description": "NUL Digital Collections API",
"repository": "https://github.com/nulib/dc-api-v2",
"author": "nulib",
Expand Down
20 changes: 9 additions & 11 deletions api/test/integration/oai.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ describe("Oai routes", () => {
expect(resultBody["OAI-PMH"].ListRecords.record)
.to.be.an("array")
.and.to.have.lengthOf(12);
const firstRecord =
resultBody["OAI-PMH"].ListRecords.record[0].header.datestamp._text;
expect(firstRecord).to.eq("2022-11-22T20:36:00Z");
});

it("validates 'from' and 'until' parameters", async () => {
Expand All @@ -117,13 +120,13 @@ describe("Oai routes", () => {
"badArgument"
);
expect(resultBody["OAI-PMH"].error["_text"]).to.eq(
"Invalid date -- make sure that 'from' or 'until' parameters are formatted as: 'YYYY-MM-DD' or 'YYYY-MM-DDThh:mm:ssZ' (with optional fractional seconds)"
"Invalid date -- make sure that 'from' or 'until' parameters are formatted as: 'YYYY-MM-DD' or 'YYYY-MM-DDThh:mm:ssZ'"
);
});

it("supports 'from' and 'until' parameters in ListRecords and ListIdentifiers verbs", async () => {
const body =
"verb=ListRecords&metadataPrefix=oai_dc&from=2022-11-22T06:16:13.791570Z&until=2022-11-22T06:16:13.791572Z";
"verb=ListRecords&metadataPrefix=oai_dc&from=2022-11-22T06:16:13Z&until=2022-11-22T06:16:15Z";
mock
.post("/dc-v2-work/_search?scroll=2m")
.reply(200, helpers.testFixture("mocks/scroll.json"));
Expand Down Expand Up @@ -169,20 +172,15 @@ describe("Oai routes", () => {
.and.to.have.lengthOf(12);
});

it("accepts OAI-PMH dates with varying fractional seconds (1-6 digits)", async () => {
it("rejects OAI-PMH dates that include fractional seconds", async () => {
const body =
"verb=ListRecords&metadataPrefix=oai_dc&from=2022-11-22T06:16:13.7Z&until=2022-11-22T06:16:13.79157Z";
mock
.post("/dc-v2-work/_search?scroll=2m")
.reply(200, helpers.testFixture("mocks/scroll.json"));
const event = helpers.mockEvent("POST", "/oai").body(body).render();
const result = await handler(event);
expect(result.statusCode).to.eq(200);
expect(result.statusCode).to.eq(400);
expect(result).to.have.header("content-type", /application\/xml/);
const resultBody = convert.xml2js(result.body, xmlOpts);
expect(resultBody["OAI-PMH"].ListRecords.record)
.to.be.an("array")
.and.to.have.lengthOf(12);
expect(resultBody["OAI-PMH"].error._attributes.code).to.eq("badArgument");
});

it("uses an empty resumptionToken to tell harvesters that list requests are complete", async () => {
Expand Down Expand Up @@ -332,7 +330,7 @@ describe("Oai routes", () => {
const resultBody = convert.xml2js(result.body, xmlOpts);
const identifyElement = resultBody["OAI-PMH"].Identify;
expect(identifyElement.earliestDatestamp._text).to.eq(
"2022-11-22T20:36:00.581418Z"
"2022-11-22T20:36:00Z"
);
expect(identifyElement.deletedRecord._text).to.eq("no");
expect(identifyElement.granularity._text).to.eq("YYYY-MM-DDThh:mm:ssZ");
Expand Down
4 changes: 2 additions & 2 deletions av-download/lambdas/package-lock.json

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

2 changes: 1 addition & 1 deletion av-download/lambdas/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lambdas",
"version": "2.8.0",
"version": "2.8.1",
"description": "Non-API handler lambdas",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
Expand Down
2 changes: 1 addition & 1 deletion chat/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "dc-api-v2-chat"
version = "2.8.0"
version = "2.8.1"
requires-python = ">=3.12"
dependencies = [
"boto3~=1.34",
Expand Down
2 changes: 1 addition & 1 deletion chat/uv.lock

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

Loading