Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ?as=iiif param to top level collections endpoint. #232

Merged
merged 1 commit into from
Jul 24, 2024
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
1 change: 1 addition & 0 deletions docs/docs/spec/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ paths:
tags:
- Collection
parameters:
- $ref: "./types.yaml#/components/parameters/as"
- $ref: "./types.yaml#/components/parameters/page"
- $ref: "./types.yaml#/components/parameters/size"
- $ref: "./types.yaml#/components/parameters/sort"
Expand Down
5 changes: 4 additions & 1 deletion node/src/api/request/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ function extractRequestedModels(requestedModels) {

function validModels(models, format) {
if (format === "iiif") {
return models.length == 1 && models.every((model) => model === "works");
return (
models.length == 1 &&
models.every((model) => model === "works" || "collections")
);
}
return models.every(isAllowed);
}
Expand Down
167 changes: 121 additions & 46 deletions node/src/api/response/iiif/collection.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const { dcApiEndpoint, dcUrl } = require("../../../environment");
const { transformError } = require("../error");
const { provider, nulLogo } = require("./presentation-api/provider");

async function transform(response, pager) {
if (response.statusCode === 200) {
Expand Down Expand Up @@ -27,32 +28,63 @@ async function buildCollection(responseBody, pageInfo) {
collectionSummary = "",
},
},
query_url,
} = pageInfo;

/**
* if the query_url pathname is `/collections` then we
* know this is a top-level "collection of collections"
*/
const { pathname } = new URL(query_url);
const isTopCollection = pathname.split("/").pop() === "collections";
const collectionId = parseCollectionId(pageInfo.query_url);

let result = {
"@context": ["http://iiif.io/api/presentation/3/context.json"],
id: collectionId(pageInfo),
id: iiifCollectionId(pageInfo),
type: "Collection",
label: { none: [collectionLabel] },
summary: { none: [collectionSummary] },
...(collectionSummary && {
summary: {
none: [`${collectionSummary}`],
},
}),
items: getItems(responseBody?.hits?.hits, pageInfo, isTopCollection),
requiredStatement: {
label: {
none: ["Attribution"],
},
value: {
none: ["Courtesy of Northwestern University Libraries"],
},
},
provider: [provider],
logo: [nulLogo],
seeAlso: [
{
id: isTopCollection
? `${dcApiEndpoint()}/collections`
: getLinkingPropertyId(pageInfo, dcApiEndpoint(), "query"),
type: "Dataset",
format: "application/json",
label: {
none: ["Northwestern University Libraries Digital Collections API"],
},
},
],
homepage: [
{
id: homepageUrl(pageInfo),
id: isTopCollection ? dcUrl() : getLinkingPropertyId(pageInfo, dcUrl()),
type: "Text",
format: "text/html",
label: {
none: [collectionLabel],
},
},
],

items: getItems(responseBody?.hits?.hits, pageInfo),
};

if (pageInfo.options?.parameterOverrides) {
const collectionId = new URL(pageInfo.query_url).pathname
.split("/")
.reverse()[0];
if (!isTopCollection && pageInfo.options?.parameterOverrides) {
const thumbnailId = `${dcApiEndpoint()}/collections/${collectionId}/thumbnail`;
result.thumbnail = [
{
Expand All @@ -68,8 +100,9 @@ async function buildCollection(responseBody, pageInfo) {
return result;
}

function getItems(hits, pageInfo) {
const items = hits.map((item) => loadItem(item["_source"]));
function getItems(hits, pageInfo, isTopCollection) {
const itemType = isTopCollection ? "Collection" : "Manifest";
const items = hits.map((item) => loadItem(item["_source"], itemType));

if (pageInfo?.next_url) {
items.push({
Expand All @@ -84,27 +117,29 @@ function getItems(hits, pageInfo) {
return items;
}

function collectionId(pageInfo) {
function iiifCollectionId(pageInfo) {
let collectionId = new URL(pageInfo.query_url);
if (pageInfo.current_page > 1) {
collectionId.searchParams.set("page", pageInfo.current_page);
}
return collectionId;
}

function homepageUrl(pageInfo) {
function parseCollectionId(query_url) {
return new URL(query_url).pathname.split("/").reverse()[0];
}

function getLinkingPropertyId(pageInfo, baseUrl, queryParam = "q") {
let result;

if (pageInfo.options?.parameterOverrides) {
const collectionId = new URL(pageInfo.query_url).pathname
.split("/")
.reverse()[0];
result = new URL(`/collections/${collectionId}`, dcUrl());
const collectionId = parseCollectionId(pageInfo.query_url);
result = new URL(`/collections/${collectionId}`, baseUrl);
} else {
result = new URL("/search", dcUrl());
result = new URL("/search", baseUrl);
if (pageInfo.options?.queryStringParameters?.query) {
result.searchParams.set(
"q",
queryParam,
pageInfo.options.queryStringParameters.query
);
}
Expand All @@ -120,36 +155,76 @@ function homepageUrl(pageInfo) {
return result;
}

function loadItem(item) {
return {
id: item.iiif_manifest,
type: "Manifest",
homepage: [
{
id: new URL(`/items/${item.id}`, dcUrl()),
type: "Text",
format: "text/html",
label: {
none: [`${item.title}`],
function loadItem(item, itemType) {
if (itemType === "Manifest") {
return {
id: item.iiif_manifest,
type: "Manifest",
homepage: [
{
id: new URL(`/items/${item.id}`, dcUrl()),
type: "Text",
format: "text/html",
label: {
none: [`${item.title}`],
},
},
],
label: {
none: [`${item.title}`],
},
],
label: {
none: [`${item.title}`],
},
summary: {
none: [`${item.work_type}`],
},
thumbnail: [
{
id: item.thumbnail,
format: "image/jpeg",
type: "Image",
width: 400,
height: 400,
summary: {
none: [`${item.work_type}`],
},
],
};
thumbnail: [
{
id: item.thumbnail,
format: "image/jpeg",
type: "Image",
width: 400,
height: 400,
},
],
};
}

if (itemType === "Collection") {
return {
id: `${item.api_link}?as=iiif`,
type: "Collection",
label: {
none: [`${item.title}`],
},
...(item.description && {
summary: {
none: [`${item.description}`],
},
}),
...(item.thumbnail && {
thumbnail: [
{
id: item.thumbnail,
type: "Image",
format: "image/jpeg",
width: 400,
height: 400,
},
],
}),
...(item.canonical_link && {
homepage: [
{
id: new URL(`/collections/${item.id}`, dcUrl()),
type: "Text",
format: "text/html",
label: {
none: [`${item.title}`],
},
},
],
}),
};
}
}

module.exports = { transform };
32 changes: 2 additions & 30 deletions node/src/api/response/iiif/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const { metadataLabelFields } = require("./presentation-api/metadata");
const {
buildPlaceholderCanvas,
} = require("./presentation-api/placeholder-canvas");
const { nulLogo, provider } = require("./presentation-api/provider");

function transform(response) {
if (response.statusCode === 200) {
Expand Down Expand Up @@ -260,37 +261,8 @@ function transform(response) {
}
}

/** Add logo manually (w/o IIIF Builder) */
const nulLogo = {
id: "https://iiif.dc.library.northwestern.edu/iiif/2/00000000-0000-0000-0000-000000000003/full/pct:50/0/default.webp",
type: "Image",
format: "image/webp",
height: 139,
width: 1190,
};
jsonManifest.logo = [nulLogo];

/** Add provider manually (w/o IIIF Builder) */
const provider = {
id: "https://www.library.northwestern.edu/",
type: "Agent",
label: { none: ["Northwestern University Libraries"] },
homepage: [
{
id: "https://dc.library.northwestern.edu/",
type: "Text",
label: {
none: [
"Northwestern University Libraries Digital Collections Homepage",
],
},
format: "text/html",
language: ["en"],
},
],
logo: [nulLogo],
};
jsonManifest.provider = [provider];
jsonManifest.logo = [nulLogo];

return {
statusCode: 200,
Expand Down
29 changes: 29 additions & 0 deletions node/src/api/response/iiif/presentation-api/provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const nulLogo = {
id: "https://iiif.dc.library.northwestern.edu/iiif/2/00000000-0000-0000-0000-000000000003/full/pct:50/0/default.webp",
type: "Image",
format: "image/webp",
height: 139,
width: 1190,
};

const provider = {
id: "https://www.library.northwestern.edu/",
type: "Agent",
label: { none: ["Northwestern University Libraries"] },
homepage: [
{
id: "https://dc.library.northwestern.edu/",
type: "Text",
label: {
none: [
"Northwestern University Libraries Digital Collections Homepage",
],
},
format: "text/html",
language: ["en"],
},
],
logo: [nulLogo],
};

module.exports = { nulLogo, provider };
26 changes: 23 additions & 3 deletions node/src/handlers/get-collections.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
const { doSearch } = require("./search-runner");
const { wrap } = require("./middleware");

const getCollections = async (event) => {
event.pathParameters.models = "collections";
event.body = { query: { match_all: {} } };
return doSearch(event, { includeToken: false });
};

const getCollectionsAsIiif = async (event) => {
event.pathParameters.models = "collections";
event.body = { query: { match_all: {} } };
event.queryStringParameters.collectionLabel =
"Northwestern University Libraries Digital Collections";
event.queryStringParameters.collectionSummary =
"Explore digital resources from the Northwestern University Library collections – including letters, photographs, diaries, maps, and audiovisual materials.";

return doSearch(event, {
includeToken: false,
parameterOverrides: { as: "iiif" },
});
};

/**
* A simple function to get Collections
*/
exports.handler = wrap(async (event) => {
event.pathParameters.models = "collections";
event.body = { query: { match_all: {} } };
return doSearch(event, { includeToken: false });
return event.queryStringParameters?.as === "iiif"
? getCollectionsAsIiif(event)
: getCollections(event);
});
26 changes: 26 additions & 0 deletions node/test/integration/get-collections.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,31 @@ describe("Collections route", () => {
const url = new URL(query_url);
expect(url.pathname).to.eq("/api/v2/search/collections");
});

it("returns top level collection as a IIIF collection", async () => {
mock
.post("/dc-v2-collection/_search", makeQuery({ size: 10, from: 0 }))
.reply(200, helpers.testFixture("mocks/collections.json"));

const event = helpers
.mockEvent("GET", "/collections")
.queryParams({ as: "iiif" })
.render();
const result = await handler(event);
expect(result.statusCode).to.eq(200);
expect(result).to.have.header(
"content-type",
/application\/json;.*charset=UTF-8/
);
const resultBody = JSON.parse(result.body);
expect(resultBody.type).to.eq("Collection");
expect(resultBody.label.none[0]).to.eq(
"Northwestern University Libraries Digital Collections"
);
expect(resultBody.summary.none[0]).to.eq(
"Explore digital resources from the Northwestern University Library collections – including letters, photographs, diaries, maps, and audiovisual materials."
);
expect(resultBody.items.length).to.eq(69);
});
});
});
Loading
Loading