@europeana/portal
Version:
Europeana Portal
243 lines (219 loc) • 8.08 kB
JavaScript
import { BASE_URL as EUROPEANA_DATA_URL } from './data.js';
import { apiError, createAxios } from './utils.js';
import thumbnail from './thumbnail.js';
import md5 from 'md5';
export const BASE_URL = process.env.EUROPEANA_ENTITY_API_URL || 'https://api.europeana.eu/entity';
export default (context = {}) => {
const $axios = createAxios({ id: 'entity', baseURL: BASE_URL }, context);
return {
$axios,
$thumbnail: thumbnail(context),
/**
* Get data for one entity from the API
* @param {string} type the type of the entity, will be normalized to the EntityAPI type if it's a human readable type
* @param {string} id the id of the entity (can contain trailing slug parts as these will be normalized)
* @return {Object[]} parsed entity data
*/
get(type, id) {
return this.$axios.get(getEntityUrl(type, id))
.then(response => ({
error: null,
entity: response.data
}))
.catch(error => {
throw apiError(error, context);
});
},
/**
* 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.$axios.get('/suggest', {
params: {
...this.$axios.defaults.params,
text,
type: 'agent,concept,timespan,organization,place',
scope: 'europeana',
...params
}
})
.then(response => response.data.items ? response.data.items : [])
.catch(error => {
throw apiError(error, context);
});
},
/**
* Lookup data for the given list of entity URIs
* @param {Array} entityUris the URIs of the entities to retrieve
* @return {Array} entity data
*/
find(entityUris) {
if (entityUris?.length === 0) {
return Promise.resolve([]);
}
const q = entityUris.join('" OR "');
const params = {
query: `entity_uri:("${q}")`,
pageSize: entityUris.length
};
return this.search(params)
.then(response => response.entities || []);
},
/**
* Return all entity subjects of type concept / agent / timespan
* @param {Object} params additional parameters sent to the API
*/
search(params = {}) {
return this.$axios.get('/search', {
params: {
...this.$axios.defaults.params,
...params
}
})
.then((response) => {
return {
entities: response.data.items ? response.data.items : [],
total: response.data.partOf ? response.data.partOf.total : null
};
})
.catch((error) => {
throw apiError(error, context);
});
},
imageUrl(entity) {
let url = null;
// `image` is a property on automated entity cards in Contentful
if (entity?.image) {
url = this.$thumbnail.edmPreview(entity.image, { size: 200 });
// `isShownBy` is a property on most entity types
} else if (entity?.isShownBy?.thumbnail) {
url = this.$thumbnail.edmPreview(entity.isShownBy.thumbnail, { size: 200 });
// `logo` is a property on organization-type entities
} else if (entity?.logo?.id) {
url = getWikimediaThumbnailUrl(entity.logo.id, 28);
}
return url;
}
};
};
/**
* Remove any additional data from the slug in order to retrieve the entity id.
* @param {string} id the id of the entity
* @return {string} retrieved id
*/
export function normalizeEntityId(id) {
return id ? id.split('-')[0] : null;
}
/**
* Construct an entity-type-specific Record API query for an entity
* @param {string} uri entity URI
* @return {string} Record API query
*/
export function getEntityQuery(uri) {
let entityQuery;
if (uri.includes('/concept/')) {
entityQuery = `skos_concept:"${uri}"`;
} else if (uri.includes('/agent/')) {
entityQuery = `edm_agent:"${uri}"`;
} else if (uri.includes('/timespan/')) {
entityQuery = `edm_timespan:"${uri}"`;
} else if (uri.includes('/organization/')) {
entityQuery = `foaf_organization:"${uri}"`;
} else if (uri.includes('/place/')) {
entityQuery = `edm_place:"${uri}"`;
} else {
throw new Error(`Unsupported entity URI "${uri}"`);
}
return entityQuery;
}
/**
* A check for a URI to see if it conforms to the entity URI pattern,
* optionally takes entity types as an array of values to check for.
* Will return true/false
* @param {string} uri A URI to check
* @param {string[]} types the entity types to check, defaults to all.
* @return {Boolean} true if the URI is a valid entity URI
*/
export function isEntityUri(uri, types) {
types = types ? types : ['concept', 'agent', 'place', 'timespan', 'organization'];
return RegExp(`^http://data\\.europeana\\.eu/(${types.join('|')})/\\d+$`).test(uri);
}
/**
* Retrieve the API name of the type using the human readable name
* @param {string} type the type of the entity
* @return {string} retrieved API name of type
*/
export function getEntityTypeApi(type) {
const names = {
place: 'place',
person: 'agent',
topic: 'concept',
time: 'timespan',
organisation: 'organization'
};
return type ? names[type] : null;
}
/**
* Retrieve the human readable of the type using the API name
* @param {string} type the type of the entity
* @return {string} retrieved human readable name of type
*/
export function getEntityTypeHumanReadable(type) {
const names = {
place: 'place',
agent: 'person',
concept: 'topic',
timespan: 'time',
organization: 'organisation'
};
return type ? names[type.toLowerCase()] : null;
}
/**
* Retrieve the URL of the entity from the human readable type and ID
* @param {string} type the human readable type of the entity either person or topic
* @param {string} id the numeric identifier of the entity, (can contain trailing slug parts as these will be normalized)
* @return {string} retrieved human readable name of type
*/
export function getEntityUrl(type, id) {
return `/${getEntityTypeApi(type)}/${normalizeEntityId(id)}.json`;
}
/**
* Retrieve the URI of the entity from the human readable type and ID
* @param {string} type the human-readable type of the entity
* @param {string} id the numeric identifier of the entity, (can contain trailing slug parts as these will be normalized)
* @return {string} retrieved human readable name of type
*/
export function getEntityUri(type, id) {
const apiType = getEntityTypeApi(type);
return `${EUROPEANA_DATA_URL}/${apiType}/${normalizeEntityId(id)}`;
}
/**
* From a URI split params as required by the portal
* @param {string} uri A URI to check
* @return {{type: String, identifier: string}} Object with the portal relevant identifiers.
*/
export function entityParamsFromUri(uri) {
const matched = uri.match(/^http:\/\/data\.europeana\.eu\/(concept|agent|place|timespan|organization)\/(\d+)$/);
const id = matched[matched.length - 1];
const type = getEntityTypeHumanReadable(matched[1]);
return { id, type };
}
/**
* The logic for going from: http://commons.wikimedia.org/wiki/Special:FilePath/[image] to
* https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/[image]/200px-[image]:
* @image {String} image URL of wikimedia image
* @size {Number} requested size of the image, default 255
* @return {String} formatted thumbnail url
*/
export function getWikimediaThumbnailUrl(image, size = 255) {
if (!(new RegExp('.wiki[mp]edia.org/wiki/Special:FilePath/').test(image))) {
return image;
}
const filename = image.split('/').pop();
const suffix = filename.endsWith('.svg') ? '.png' : '';
const underscoredFilename = decodeURIComponent(filename).replace(/ /g, '_');
const hash = md5(underscoredFilename);
return `https://upload.wikimedia.org/wikipedia/commons/thumb/${hash.substring(0, 1)}/${hash.substring(0, 2)}/${underscoredFilename}/${size}px-${underscoredFilename}${suffix}`;
}