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

Deploy API v2.1.0 to production #142

Merged
merged 10 commits into from
May 23, 2023
9 changes: 7 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ on:
- "src/**"
- "template.yaml"
workflow_dispatch:
inputs:
force_deploy_docs:
description: Deploy documentation even if no changes detected
type: boolean
default: false
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
env:
Expand Down Expand Up @@ -70,7 +75,7 @@ jobs:
docs/*
publish-docs:
needs: docs-changed
if: ${{ needs.docs-changed.outputs.result == 'true' }}
if: ${{ needs.docs-changed.outputs.result == 'true' || inputs.force_deploy_docs }}
runs-on: ubuntu-latest
permissions:
id-token: write
Expand All @@ -88,7 +93,7 @@ jobs:
python-version: 3.9
- uses: abatilo/actions-poetry@v2
with:
poetry-version: 1.1.14
poetry-version: 1.4.2
- name: Install dependencies
run: poetry install
working-directory: ./docs
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/production-types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ on:
paths:
- "docs/docs/spec/data-types.yaml"
- ".github/flags/deploy-data-types"
- ".github/workflows/production-types.yml"
workflow_dispatch:
jobs:
Push-To-Types-Repo-Production:
Expand All @@ -26,7 +25,7 @@ jobs:
GH_TOKEN: ${{secrets.GH_TOKEN}}
steps:
- run: |
echo PR with changes to datatypes.yml was merged into staging
echo PR with changes to datatypes.yml was merged into main
- name: Checkout dc-api-v2
uses: actions/checkout@v3
with:
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/staging-types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ on:
paths:
- "docs/docs/spec/data-types.yaml"
- ".github/flags/deploy-data-types"
- ".github/workflows/staging-types.yml"
workflow_dispatch:
jobs:
Push-To-Types-Repo-Staging:
Expand Down
6 changes: 5 additions & 1 deletion docs/docs/spec/data-types.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -251,11 +251,15 @@ components:
- id
- label
Info:
description: Global DC API properties
description: Additional Information
type: object
properties:
description:
type: string
link_expiration:
type: string
format: date-time
nullable: true
name:
type: string
version:
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dc-api-build",
"version": "2.0.1",
"version": "2.1.0",
"description": "NUL Digital Collections API Build Environment",
"repository": "https://github.com/nulib/dc-api-v2",
"author": "nulib",
Expand Down Expand Up @@ -51,4 +51,4 @@
]
}
}
}
}
21 changes: 21 additions & 0 deletions src/api/response/iiif/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ const {
isAudioVideo,
} = require("./presentation-api/items");
const { metadataLabelFields } = require("./presentation-api/metadata");
const {
buildPlaceholderCanvas,
} = require("./presentation-api/placeholder-canvas");

function transform(response) {
if (response.statusCode === 200) {
Expand Down Expand Up @@ -234,6 +237,24 @@ function transform(response) {
}
});

/**
* Add a placeholderCanvas property to a Canvas if the annotation body is of type "Image"
* (iiif-builder package currently doesn't support adding this property)
*/
for (let i = 0; i < jsonManifest.items.length; i++) {
if (jsonManifest.items[i].items[0].items[0].body.type === "Image") {
const { id, thumbnail } = jsonManifest.items[i];
const placeholderFileSet = source.file_sets.find(
(fileSet) =>
fileSet.representative_image_url === thumbnail[0].service[0]["@id"]
);
jsonManifest.items[i].placeholderCanvas = buildPlaceholderCanvas(
id,
placeholderFileSet
);
}
}

return {
statusCode: 200,
headers: {
Expand Down
7 changes: 5 additions & 2 deletions src/api/response/iiif/presentation-api/items.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ function annotationType(workType) {
}

function buildAnnotationBody(fileSet, workType) {
const bodyType = annotationType(workType);
const body = {
id: buildAnnotationBodyId(fileSet, workType),
type: annotationType(workType),
format: fileSet.mime_type,
type: bodyType,
format: isAudioVideo(bodyType)
? "application/x-mpegurl"
: fileSet.mime_type,
height: fileSet.height || 100,
width: fileSet.width || 100,
};
Expand Down
54 changes: 54 additions & 0 deletions src/api/response/iiif/presentation-api/placeholder-canvas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
function buildPlaceholderCanvas(id, fileSet, size = 640) {
const { representative_image_url } = fileSet;
const { placeholderWidth, placeholderHeight } = getPlaceholderSizes(
fileSet,
size
);

return {
id: `${id}/placeholder`,
type: "Canvas",
width: placeholderWidth,
height: placeholderHeight,
items: [
{
id: `${id}/placeholder/annotation-page/0`,
type: "AnnotationPage",
items: [
{
id: `${id}/placeholder/annotation/0`,
type: "Annotation",
motivation: "painting",
body: {
id: `${representative_image_url}/full/!${placeholderWidth},${placeholderHeight}/0/default.jpg`,
type: "Image",
format: fileSet.mime_type,
width: placeholderWidth,
height: placeholderHeight,
service: [
{
["@id"]: representative_image_url,
["@type"]: "ImageService2",
profile: "http://iiif.io/api/image/2/level2.json",
},
],
},
target: `${id}/placeholder`,
},
],
},
],
};
}

function getPlaceholderSizes(fileset, size) {
const { width, height } = fileset;
const placeholderWidth = width > size ? size : width;
const placeholderHeight = Math.floor((placeholderWidth / width) * height);
return { placeholderWidth, placeholderHeight };
}

module.exports = {
buildPlaceholderCanvas,
getPlaceholderSizes,
};
14 changes: 7 additions & 7 deletions src/api/response/opensearch/index.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
const { appInfo } = require("../../../environment");
const { transformError } = require("../error");

async function transform(response, pager) {
async function transform(response, options = {}) {
if (response.statusCode === 200) {
const responseBody = JSON.parse(response.body);
return await (responseBody?.hits?.hits
? transformMany(responseBody, pager)
: transformOne(responseBody));
? transformMany(responseBody, options)
: transformOne(responseBody, options));
}
return transformError(response);
}

async function transformOne(responseBody) {
async function transformOne(responseBody, options = {}) {
return {
statusCode: 200,
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
data: responseBody._source,
info: appInfo(),
info: appInfo(options),
}),
};
}

async function transformMany(responseBody, pager) {
async function transformMany(responseBody, options) {
return {
statusCode: 200,
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
data: extractSource(responseBody.hits.hits),
pagination: await paginationInfo(responseBody, pager),
pagination: await paginationInfo(responseBody, options?.pager),
info: appInfo(),
aggregations: responseBody.aggregations,
}),
Expand Down
2 changes: 1 addition & 1 deletion src/api/response/transformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ async function transformSearchResult(response, pager) {
return await iiifCollectionResponse.transform(response, pager);
}

return await opensearchResponse.transform(response, pager);
return await opensearchResponse.transform(response, { pager: pager });
}
return transformError(response);
}
Expand Down
4 changes: 3 additions & 1 deletion src/environment.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const exp = require("constants");
const fs = require("fs");
const jwt = require("jsonwebtoken");
const path = require("path");
Expand All @@ -22,11 +23,12 @@ function apiTokenSecret() {
return process.env.API_TOKEN_SECRET;
}

function appInfo() {
function appInfo(options = {}) {
return {
name: PackageInfo.name,
description: PackageInfo.description,
version: PackageInfo.version,
link_expiration: options.expires || null,
};
}

Expand Down
6 changes: 3 additions & 3 deletions src/handlers/get-shared-link-by-id.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ exports.handler = wrap(async (event) => {
});
if (workResponse.statusCode !== 200) return invalidRequest("Not Found");

// add entitlement for the work id
// TODO make part of request/response processing
event.userToken.addEntitlement(workId);
return await opensearchResponse.transform(workResponse);
return await opensearchResponse.transform(workResponse, {
expires: expirationDate,
});
});

const invalidRequest = (message) => {
Expand Down
2 changes: 1 addition & 1 deletion src/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dc-api",
"version": "2.0.1",
"version": "2.1.0",
"description": "NUL Digital Collections API",
"repository": "https://github.com/nulib/dc-api-v2",
"author": "nulib",
Expand Down
8 changes: 8 additions & 0 deletions test/unit/api/response/iiif/manifest.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ describe("Image Work as IIIF Manifest response transformer", () => {
);
});

it("adds a placeholderCanvas property to Image canvases", async () => {
const { manifest } = await setup();
const { placeholderCanvas } = manifest.items[0];
expect(placeholderCanvas.id).to.eq(`${manifest.items[0].id}/placeholder`);
expect(placeholderCanvas.type).to.eq("Canvas");
});

it("excludes Preservation and Supplemental filesets", async () => {
const { manifest } = await setup();
manifest.items.forEach((canvas) => {
Expand Down Expand Up @@ -190,6 +197,7 @@ describe("A/V Work as IIIF Manifest response transformer", () => {

expect(annotation.body.duration).to.eq(5.599);
expect(annotation.body.type).to.eq("Video");
expect(annotation.body.format).to.eq("application/x-mpegurl");
expect(annotation.body.id).to.eq(source.file_sets[0].streaming_url);
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"use strict";

const chai = require("chai");
const expect = chai.expect;
const transformer = requireSource("api/response/iiif/manifest");
const { buildPlaceholderCanvas, getPlaceholderSizes } = requireSource(
"api/response/iiif/presentation-api/placeholder-canvas"
);

describe("IIIF response presentation API placeholderCanvas helpers", () => {
async function setup() {
const response = {
statusCode: 200,
body: helpers.testFixture("mocks/work-1234.json"),
};
const source = JSON.parse(response.body)._source;

const result = await transformer.transform(response);
expect(result.statusCode).to.eq(200);

return { source, manifest: JSON.parse(result.body) };
}

it("buildPlaceholderCanvas(value)", async () => {
const { source, manifest } = await setup();
const id = manifest.items[0].id;
const fileSet = source.file_sets[0];
const placeholder = buildPlaceholderCanvas(id, fileSet, 640);

expect(placeholder.id).to.eq(`${id}/placeholder`);
expect(placeholder.type).to.eq("Canvas");
expect(placeholder.width).to.eq(640);
expect(placeholder.height).to.eq(480);
expect(placeholder.items[0].id).to.eq(
`${id}/placeholder/annotation-page/0`
);
expect(placeholder.items[0].type).to.eq("AnnotationPage");
expect(placeholder.items[0].items[0].type).to.eq("Annotation");
expect(placeholder.items[0].items[0].motivation).to.eq("painting");
expect(placeholder.items[0].items[0].body.id).to.eq(
`${fileSet.representative_image_url}/full/!640,480/0/default.jpg`
);
expect(placeholder.items[0].items[0].body.type).to.eq("Image");
expect(placeholder.items[0].items[0].body.format).to.eq(fileSet.mime_type);
expect(placeholder.items[0].items[0].body.width).to.eq(640);
expect(placeholder.items[0].items[0].body.height).to.eq(480);
expect(placeholder.items[0].items[0].body.service[0]["@id"]).to.eq(
fileSet.representative_image_url
);
});

it("getPlaceholderSizes(fileSet, size)", () => {
const fileSet = {
width: 3125,
height: 2240,
};

const { placeholderHeight, placeholderWidth } = getPlaceholderSizes(
fileSet,
1000
);

expect(placeholderWidth).to.eq(1000);
expect(placeholderHeight).to.eq(716);
});
});
Loading