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

refactor: create Europeana APIs package #2325

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ jobs:
-
name: Check image size
env:
DOCKER_IMAGE_SIZE_LIMIT: '360M'
DOCKER_IMAGE_SIZE_LIMIT: '370M'
run: |
docker_image_size=$(docker image inspect --format '{{.Size}}' ${{ needs.metadata.outputs.docker-full-tag }})
if [ ${docker_image_size} -gt $(numfmt --from=si ${DOCKER_IMAGE_SIZE_LIMIT}) ]; then
Expand Down
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ export default {
'text'
],
projects: [
'<rootDir>/packages/apis',
'<rootDir>/packages/i18n',
'<rootDir>/packages/oembed',
'<rootDir>/packages/portal',
'<rootDir>/packages/utils',
'<rootDir>/packages/vue-router-query',
'<rootDir>/packages/vue-session',
'<rootDir>/packages/vue-visible-on-scroll'
Expand Down
9,237 changes: 4,885 additions & 4,352 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@
"version": "node bin/sonar-project-version.js && git add sonar-project.properties"
},
"resolutions": {
"@europeana/apis": "link:./packages/apis",
"@europeana/contentful": "link:./packages/contentful",
"@europeana/i18n": "link:./packages/i18n",
"@europeana/mirador": "link:./packages/mirador",
"@europeana/oembed": "link:./packages/oembed",
"@europeana/portal": "link:./packages/portal",
"@europeana/style": "link:./packages/style",
"@europeana/utils": "link:./packages/utils",
"@europeana/vue-router-query": "link:./packages/vue-router-query",
"@europeana/vue-session": "link:./packages/vue-session",
"@europeana/vue-visible-on-scroll": "link:./packages/vue-visible-on-scroll"
Expand Down
9 changes: 9 additions & 0 deletions packages/apis/babel.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Babel config used by Jest, e.g. for unit tests
module.exports = {
presets: [
[
'@babel/preset-env',
{ targets: { node: 'current' } }
]
]
};
21 changes: 21 additions & 0 deletions packages/apis/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export default {
displayName: '@europeana/apis',
moduleFileExtensions: [
'js',
'json',
'mjs'
],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
setupFiles: [
'<rootDir>/tests/setup.js'
],
testEnvironment: 'jsdom',
testPathIgnorePatterns: [
'/node_modules/'
],
transform: {
'^.+\\.(js|mjs)$': 'babel-jest'
}
};
17 changes: 17 additions & 0 deletions packages/apis/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@europeana/apis",
"version": "1.150.0",
"description": "Europeana API clients",
"license": "EUPL-1.2",
"type": "module",
"main": "./src/index.js",
"dependencies": {
"@europeana/i18n": "1.150.0",
"@europeana/utils": "1.150.0",
"axios": "^0.21.4",
"deepmerge": "^4.2.2",
"lodash": "^4.17.21",
"md5": "^2.3.0",
"qs": "^6.10.1"
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import EuropeanaApi from './apis/base.js';
import EuropeanaApi from './base.js';

export default class EuropeanaAnnotationApi extends EuropeanaApi {
static ID = 'annotation';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import axios from 'axios';
import qs from 'qs';

import { keycloakResponseErrorHandler } from '../auth.js';
import EuropeanaApiContextConfig from './config/context.js';
import EuropeanaApiEnvConfig from './config/env.js';

export default class EuropeanaApi {
static ID;
Expand All @@ -13,16 +12,15 @@ export default class EuropeanaApi {
static BASE_URL = 'https://api.europeana.eu/';
#axios;

constructor(context) {
this.context = context;
this.config = new EuropeanaApiContextConfig(this.constructor.ID, context);
constructor(config = {}) {
this.config = {
...(new EuropeanaApiEnvConfig(this.constructor.ID)),
...config
};
}

get axios() {
if (!this.#axios) {
this.#axios = this.createAxios();
}
return this.#axios;
return this.#axios || this.createAxios();
}

get baseURL() {
Expand Down Expand Up @@ -55,45 +53,19 @@ export default class EuropeanaApi {
return error;
}

createAxios() {
const axiosBase = (this.constructor.AUTHORISING && this.context?.$axios) ? this.context?.$axios : axios;
const axiosInstance = axiosBase.create(this.axiosInstanceOptions);

axiosInstance.interceptors.request.use(this.rewriteAxiosRequestUrl.bind(this));

const app = this.context?.app;
if (app?.$axiosLogger) {
axiosInstance.interceptors.request.use(app.$axiosLogger);
}

if (this.constructor.AUTHORISING && (typeof axiosInstance.onResponseError === 'function')) {
axiosInstance.onResponseError(error => keycloakResponseErrorHandler(this.context, error));
}

return axiosInstance;
createAxios(axiosBase = axios) {
this.#axios = axiosBase.create(this.axiosInstanceOptions);
return this.#axios;
}

request(config) {
return this.axios({
...config,
params: {
...this.axios.defaults.params,
...config.params || {}
}
})
return this.axios.request(config)
.then((response) => response.data)
.catch((error) => {
throw this.apiError(error);
});
}

rewriteAxiosRequestUrl(requestConfig) {
if (this.config.urlRewrite) {
requestConfig.baseURL = this.config.urlRewrite;
}
return requestConfig;
}

get axiosInstanceOptions() {
const params = {};
if (this.constructor.AUTHENTICATING) {
Expand All @@ -106,6 +78,7 @@ export default class EuropeanaApi {
paramsSerializer(params) {
return qs.stringify(params, { arrayFormat: 'repeat' });
},
// TODO: make env-configurable
timeout: 10000
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import snakeCase from 'lodash/snakeCase.js';

export default class EuropeanaApiEnvConfig {
constructor(id, scope) {
constructor(id) {
this.id = id;
this.scope = scope;

this.key = this.keyFromEnv;
this.url = this.urlFromEnv;
this.urlRewrite = this.urlRewriteFromEnv;
}

env(prop, { shared = false } = {}) {
Expand All @@ -26,23 +24,11 @@ export default class EuropeanaApiEnvConfig {
return this.env('url', { shared: false });
}

get urlRewriteFromEnv() {
let urlRewriteFromEnv;

if (this.scope === 'private') {
urlRewriteFromEnv = this.env('urlPrivate', { shared: false });
}

return urlRewriteFromEnv;
}

toJSON() {
return JSON.stringify({
key: this.key,
id: this.id,
scope: this.scope,
url: this.url,
urlRewrite: this.urlRewrite
url: this.url
});
}
}
12 changes: 12 additions & 0 deletions packages/apis/src/data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import EuropeanaApi from './base.js';

export const EUROPEANA_DATA_API_BASE_URL = 'http://data.europeana.eu';
export const EUROPEANA_DATA_API_ITEM_URL_PREFIX = `${EUROPEANA_DATA_API_BASE_URL}/item`;
export const EUROPEANA_DATA_API_SET_URL_PREFIX = `${EUROPEANA_DATA_API_BASE_URL}/set`;

export default class EuropeanaDataApi extends EuropeanaApi {
static ID = 'data';
static BASE_URL = EUROPEANA_DATA_API_BASE_URL;
static ITEM_URL_PREFIX = EUROPEANA_DATA_API_ITEM_URL_PREFIX;
static SET_URL_PREFIX = EUROPEANA_DATA_API_SET_URL_PREFIX;
}
92 changes: 92 additions & 0 deletions packages/apis/src/entity/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import EuropeanaApi from '../base.js';

export const EUROPEANA_ENTITY_API_BASE_URL = 'https://api.europeana.eu/entity';
export const EUROPEANA_ENTITY_API_ENTITY_TYPES = [
{ id: 'agent', qf: 'edm_agent', slug: 'person' },
{ id: 'concept', qf: 'skos_concept', slug: 'topic' },
{ id: 'organization', qf: 'foaf_organization', slug: 'organisation' },
{ id: 'place', qf: 'edm_place', slug: 'place' },
{ id: 'timespan', qf: 'edm_timespan', slug: 'time' }
];

export default class EuropeanaEntityApi extends EuropeanaApi {
static ID = 'entity';
static BASE_URL = EUROPEANA_ENTITY_API_BASE_URL;
static AUTHENTICATING = true;

/**
* Get data for one entity from the API
* @param {string} type the type of the entity
* @param {string|number} id the id of the entity
* @return {Object[]} parsed entity data
*/
get(type, id) {
return this.request({
method: 'get',
url: `/${type}/${id}.json`
});
}

/**
* Get entity suggestions from the API
* @param {string} text the query text to supply suggestions for
* @param {Object} params additional parameters sent to the API
*/
suggest(text, params = {}) {
return this.request({
method: 'get',
url: '/suggest',
params: {
text,
type: 'agent,concept,timespan,organization,place',
scope: 'europeana',
...params
}
})
.then((response) => response.items || []);
}

/**
* Lookup data for the given list of entity URIs
* @param {Array} entityUris the URIs of the entities to retrieve
* @param {Object} params additional parameters sent to the API
* @return {Array} entity data
*/
async find(entityUris, params = {}) {
if (entityUris?.length === 0) {
return Promise.resolve([]);
}

const entities = await this.retrieve(entityUris, params);

return (entities || [])
// Preserve original order from arg
.sort((a, b) => {
const indexForA = entityUris.findIndex((uri) => a.id === uri);
const indexForB = entityUris.findIndex((uri) => b.id === uri);
return indexForA - indexForB;
});
}

retrieve(entityUris, params = {}) {
return this.request({
method: 'post',
url: '/retrieve',
data: entityUris,
params
})
.then((response) => response.items);
}

/**
* Return all entity subjects of type concept / agent / timespan
* @param {Object} params additional parameters sent to the API
*/
search(params = {}) {
return this.request({
method: 'get',
url: '/search',
params
});
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import EuropeanaApi from './apis/base.js';
import { BASE_URL as EUROPEANA_DATA_URL } from './data.js';
import EuropeanaEntityApi from './entity.js';
import EuropeanaApi from '../base.js';
import EuropeanaEntityApi from './index.js';
import EuropeanaDataApi from '../data.js';

export default class EuropeanaEntityManagementApi extends EuropeanaApi {
static ID = 'entityManagement';
Expand All @@ -23,7 +23,7 @@ export default class EuropeanaEntityManagementApi extends EuropeanaApi {

return this.request({
method: 'get',
url: id.replace(EUROPEANA_DATA_URL, ''),
url: id.replace(EuropeanaDataApi.BASE_URL, ''),
params
});
}
Expand All @@ -37,7 +37,7 @@ export default class EuropeanaEntityManagementApi extends EuropeanaApi {
update(id, data) {
return this.request({
method: 'put',
url: id.replace(EUROPEANA_DATA_URL, ''),
url: id.replace(EuropeanaDataApi.BASE_URL, ''),
data,
timeout: 15000
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import EuropeanaApi from './apis/base.js';
import EuropeanaApi from './base.js';

export default class EuropeanaFulltextApi extends EuropeanaApi {
static ID = 'fulltext';
Expand Down
8 changes: 8 additions & 0 deletions packages/apis/src/iiif/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import EuropeanaApi from '../base.js';

export const EUROPEANA_IIIF_API_BASE_URL = 'https://iiif.europeana.eu';

export default class EuropeanaIiifImageApi extends EuropeanaApi {
static ID = 'iiifImage';
static BASE_URL = `${EUROPEANA_IIIF_API_BASE_URL}/image`;
}
7 changes: 7 additions & 0 deletions packages/apis/src/iiif/presentation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import EuropeanaApi from '../base.js';
import { EUROPEANA_IIIF_API_BASE_URL } from './index.js';

export default class EuropeanaIiifPresentationApi extends EuropeanaApi {
static ID = 'iiifPresentation';
static BASE_URL = `${EUROPEANA_IIIF_API_BASE_URL}/presentation`;
}
Loading
Loading