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

feat(browseRequests): add browse endpoints with test and documentation in progress #297

Merged
merged 52 commits into from
Jun 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
4947de4
feat(tests): add tests for browse endpoints
Aug 4, 2019
27dcc96
refactor(tests): modify test according to new response structure
akhilesh26 Aug 12, 2019
4413ff0
fix: browse edition group for a publisher
akhilesh26 Aug 12, 2019
c7c4947
test: Add relationships to author browse tests
MonkeyDo Aug 14, 2019
66b4d49
test: Create entities along with relationships
MonkeyDo Aug 14, 2019
496f117
try to get right response
akhilesh26 Aug 16, 2019
502a7e6
fix: reduce error and clean code
akhilesh26 Aug 16, 2019
0beabb0
refactor: some updates for getting correct response of author browse …
akhilesh26 Aug 17, 2019
1d87a77
feat: implement endpoint for browse work and author with document
akhilesh26 Aug 18, 2019
c3d7dc6
feat: add browse enpoint for publisher
akhilesh26 Aug 19, 2019
572a049
feat: add browse endpoint for edition-group initail
akhilesh26 Aug 19, 2019
a5fd8ab
feat: implement browse endpoint for edition initial
akhilesh26 Aug 19, 2019
10a37b0
fix: resolve typo of `found` at `entity not found`
akhilesh26 Aug 19, 2019
d3182fe
feat: Fetch entities for browse request relationships
MonkeyDo Aug 19, 2019
7e2a170
test: Fix lint error
MonkeyDo Aug 19, 2019
2ccf8d9
feat: refactor browse request query params validator
MonkeyDo Aug 20, 2019
6437f73
feat: Load correct entity from relationship in browse requests
MonkeyDo Aug 20, 2019
5d5271d
feat: filter author browse requests by type
MonkeyDo Aug 20, 2019
4734e90
refactor: update browse endpoints for query params validation
akhilesh26 Aug 21, 2019
2cec63b
refactor: update filter validation for each entity type, update relat…
akhilesh26 Aug 21, 2019
4c9ce56
test: Catch errors when creating test entities that already exist
MonkeyDo Aug 24, 2019
3f8bc87
test: Correct author browse request tests
MonkeyDo Aug 24, 2019
3923652
refactor: modify author brouse tests
akhilesh26 Aug 25, 2019
176b838
refactor: add publisher on test and route for author relation
akhilesh26 Aug 26, 2019
bfbe6ab
test: Catch duplicate authorType creation
MonkeyDo Aug 26, 2019
fbd0e02
refactor: modify work browse tests and add more
akhilesh26 Aug 26, 2019
f8d391b
chore: refactor relationshipsFilterMethod
prabalsingh24 Apr 24, 2020
0719ce5
fix the edition-editionGroup relationship
prabalsingh24 Apr 24, 2020
a2d3402
fix: allow params to be case insensitive
prabalsingh24 Apr 26, 2020
d0db5ba
test: add tests for Browse Work
prabalsingh24 Apr 26, 2020
e4e2245
test: add tests for browse author
prabalsingh24 Apr 26, 2020
5b80c3b
tests: add tests for browse edition
prabalsingh24 Apr 27, 2020
ac20c21
tests: add tests for browse edition-group
prabalsingh24 Apr 27, 2020
23e250d
fix: fix the failed tests
prabalsingh24 Apr 27, 2020
5ad5e19
tests : add tests for browse publisher
prabalsingh24 Apr 27, 2020
b91bbe6
relationship: fix publisher-edition relationship
prabalsingh24 May 6, 2020
4093dbe
tests: add test for edition-publisher relationship
prabalsingh24 May 6, 2020
6ee4f0f
fix: change 406 error to 400 error for invalid bbid
prabalsingh24 May 6, 2020
a66fd02
test: add tests for no related entity
prabalsingh24 May 7, 2020
dc25372
docs: make changes in the swagger docs
prabalsingh24 May 7, 2020
16ce3c4
docs: polish up swagger docs
MonkeyDo Jun 17, 2020
f97c1b8
docs: Fix swagger docs
MonkeyDo Jun 17, 2020
918c95d
refactor(api): don't load Edition twice in EG brose request
MonkeyDo Jun 17, 2020
b5a70ef
refactor(api): Require entity in loadEntity util
MonkeyDo Jun 17, 2020
0ff10d8
refactor(api browse): Load browsed entities with related items
MonkeyDo Jun 18, 2020
6102b11
refactor(api): Use ISO codes for languages
MonkeyDo Jun 18, 2020
56b5666
test(api): Test route error when browsing by multiple entities
MonkeyDo Jun 18, 2020
dec997c
refactor(api): Use ISO codes from alias languages
MonkeyDo Jun 18, 2020
e791a62
Merge branch 'master' into pr/297
MonkeyDo Jun 18, 2020
6f90f7a
test(api): Fix author creation helper
MonkeyDo Jun 19, 2020
5db80e2
refactor(api): Don't import all of lodash
MonkeyDo Jun 19, 2020
f0808f4
refactor(tests): Small refactor for test work creation
MonkeyDo Jun 19, 2020
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 .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"command": "${workspaceFolder}/node_modules/.bin/babel",
"args": [
"src",
"--ignore 'src/server','src/client'",
"--ignore", "src/server","src/client",
"--source-maps",
"--out-dir",
"lib"
Expand Down
12 changes: 7 additions & 5 deletions src/api/helpers/entityLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,25 @@ import * as commonUtils from '../../common/helpers/utils';
* If BBID is valid then extract the entity data from database by using BBID and relations of
* that entity. If entity is found succesfully then set that entity data to the res.locals.entity
* otherwise return an object {message: errMessage} as response with status code 404.
* If the BBID is not valid then return a status code 406 and an object {message: 'BBID is not valid uuid'}.
* If the BBID is not valid then return a status code 400 and an object {message: 'BBID is not valid uuid'}.
*/


export function makeEntityLoader(modelName, relations, errMessage) {
export function makeEntityLoader(modelName, relations, errMessage, isBrowse) {
return async (req, res, next) => {
const {orm} = req.app.locals;
if (commonUtils.isValidBBID(req.params.bbid)) {
const bbid = isBrowse ? req.query.bbid : req.params.bbid;
const model = isBrowse ? req.query.modelType : modelName;
if (commonUtils.isValidBBID(bbid)) {
try {
const entityData = await orm.func.entity.getEntity(orm, modelName, req.params.bbid, relations);
const entityData = await orm.func.entity.getEntity(orm, model, bbid, relations);
res.locals.entity = entityData;
return next();
}
catch (err) {
return res.status(404).send({message: errMessage});
}
}
return res.status(406).send({message: 'BBID is not valid uuid'});
return res.status(400).send({message: 'BBID is not valid uuid'});
};
}
36 changes: 18 additions & 18 deletions src/api/helpers/formatEntityData.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ import _ from 'lodash';
* @example
* getDefaultAlias(entity);
* /* => {
"aliasLanguage": "English",
"language": "eng",
"name": "H. Beam Piper",
"sortName": "Piper, H. Beam"
}
*/

function getDefaultAlias(entity: object) {
return {
aliasLanguage: _.get(entity, 'defaultAlias.language.name', null),
language: _.get(entity, 'defaultAlias.language.isoCode3', null),
name: _.get(entity, 'defaultAlias.name', null),
primary: _.get(entity, 'defaultAlias.primary', null),
sortName: _.get(entity, 'defaultAlias.sortName', null)
Expand All @@ -50,16 +50,16 @@ function getDefaultAlias(entity: object) {
* A function to extract the languages from ORM entity
* @function
* @param {object} entity - an ORM entity
* @returns {string[]} an array containing languages of an ORM entity.
* @returns {string[]} an array containing three letter ISO 639-3 language codes of an ORM entity.
* By default it return []
*
* @example
* getLanguages(entity);
* /* => ['Hindi', 'English', 'Spanish']
* /* => ['hin', 'eng', 'spa']
*/

function getLanguages(entity: object) {
return _.get(entity, 'languageSet.languages', []).map((language) => language.name);
return _.get(entity, 'languageSet.languages', []).map(({isoCode3}) => isoCode3);
}

/**
Expand All @@ -75,15 +75,15 @@ function getLanguages(entity: object) {
* /* => {
"bbid": "ba446064-90a5-447b-abe5-139be547da2e",
"defaultAlias": {
"aliasLanguage": "English",
"language": "eng",
"name": "Harry Potter",
"primary": true,
"sortName": "Harry Potter"
},
"disambiguation": null,
"entityType": "Work",
"languages": [
"English"
"eng"
],
"workType": "Epic"
}
Expand Down Expand Up @@ -114,7 +114,7 @@ export function getWorkBasicInfo(work: object) {
* /* => {
"bbid": "442ab642-985a-4957-9d61-8a1d9e82de1f",
"defaultAlias": {
"aliasLanguage": "English",
"language": "eng",
"name": "A Monster Calls",
"primary": true,
"sortName": "Monster Calls, A"
Expand All @@ -124,7 +124,7 @@ export function getWorkBasicInfo(work: object) {
"editionFormat": "eBook",
"height": null,
"languages": [
"English"
"eng"
],
"pages": 214,
"releaseEventDates": [
Expand Down Expand Up @@ -167,7 +167,7 @@ export function getEditionBasicInfo(edition: object) {
* /* => {
"bbid": "3889b695-70d5-4933-9f08-defad217623e",
"defaultAlias": {
"aliasLanguage": "English",
"language": "eng",
"name": "A Suitable Boy",
"primary": true,
"sortName": "Suitable Boy, A"
Expand All @@ -183,7 +183,7 @@ export function getEditionGroupBasicInfo(editionGroup: object) {
bbid: _.get(editionGroup, 'bbid', null),
defaultAlias: getDefaultAlias(editionGroup),
disambiguation: _.get(editionGroup, 'disambiguation.comment', null),
type: _.get(editionGroup, 'editionGroupType.label')
editionGroupType: _.get(editionGroup, 'editionGroupType.label')
};
}

Expand All @@ -202,7 +202,7 @@ export function getEditionGroupBasicInfo(editionGroup: object) {
"beginArea": null,
"beginDate": "1904-03-23",
"defaultAlias": {
"aliasLanguage": "English",
"language": "eng",
"name": "H. Beam Piper",
"primary": true,
"sortName": "Piper, H. Beam"
Expand All @@ -219,6 +219,7 @@ export function getEditionGroupBasicInfo(editionGroup: object) {
export function getAuthorBasicInfo(author: object) {
return _.isNil(author) ? null :
{
authorType: _.get(author, 'authorType.label', null),
bbid: _.get(author, 'bbid', null),
beginArea: _.get(author, 'beginArea.name', null),
beginDate: _.get(author, 'beginDate', null),
Expand All @@ -227,8 +228,7 @@ export function getAuthorBasicInfo(author: object) {
endArea: _.get(author, 'endArea.name', null),
endDate: _.get(author, 'endDate', null),
ended: _.get(author, 'ended', null),
gender: _.get(author, 'gender.name', null),
type: _.get(author, 'authorType.label', null)
gender: _.get(author, 'gender.name', null)
};
}

Expand All @@ -247,7 +247,7 @@ export function getAuthorBasicInfo(author: object) {
"bbid": "e418874e-5684-4fe9-9d2d-1b7e5d43fd59",
"beginDate": "1943",
"defaultAlias": {
"aliasLanguage": "[Multiple languages]",
"language": "mul",
"name": "Bharati Bhawan",
"primary": true,
"sortName": "Bhawan, Bharati"
Expand All @@ -269,7 +269,7 @@ export function getPublisherBasicInfo(publisher: object) {
disambiguation: _.get(publisher, 'disambiguation.comment', null),
endDate: _.get(publisher, 'endDate', null),
ended: _.get(publisher, 'ended', null),
type: _.get(publisher, 'publisherType.label', null)
publisherType: _.get(publisher, 'publisherType.label', null)
};
}

Expand All @@ -286,7 +286,7 @@ export function getPublisherBasicInfo(publisher: object) {
* /* => {
"aliases": [
{
"aliasLanguage": "English",
"language": "eng",
"name": "A Monster Calls",
"primary": true,
"sortName": "Monster Calls, A"
Expand All @@ -300,7 +300,7 @@ export function getEntityAliases(entity: object) {
return _.isNil(entity) ? null :
{
aliases: _.get(entity, 'aliasSet.aliases', []).map((alias) => ({
aliasLanguage: _.get(alias, 'language.name', null),
language: _.get(alias, 'language.isoCode3', null),
name: _.get(alias, 'name', null),
primary: _.get(alias, 'primary', null),
sortName: _.get(alias, 'sortName', null)
Expand Down
84 changes: 84 additions & 0 deletions src/api/helpers/middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright (C) 2019 Akhilesh Kumar
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/


import * as commonUtils from '../../common/helpers/utils';
import _ from 'lodash';


export function validateBrowseRequestQueryParameters(validLinkedEntities) {
// eslint-disable-next-line consistent-return
return (req, res, next) => {
const queriedEntities = validLinkedEntities.filter(keyName => _.has(req.query, keyName));

if (queriedEntities.length < 1 || queriedEntities.length > 1) {
const errorMessage =
`Browse requests require exactly 1 linked entity in query parameters; you passed ${queriedEntities.length}: ${queriedEntities}`;
return res.status(400).send({message: errorMessage});
}
const queriedEntityType = queriedEntities[0];
req.query.bbid = req.query[queriedEntityType];
req.query.modelType = _.upperFirst(_.camelCase(queriedEntityType));
next();
};
}

export async function loadEntity(orm, relEntity, fetchRelated) {
const model = commonUtils.getEntityModelByType(orm, relEntity.type);
try {
const entity = await model.forge({bbid: relEntity.bbid})
.fetch({require: true, withRelated: ['defaultAlias', 'disambiguation'].concat(fetchRelated || [])});
return entity.toJSON();
}
catch (error) {
return null;
}
}

export function loadEntityRelationshipsForBrowse() {
return async (req, res, next) => {
const {orm} = req.app.locals;
const {RelationshipSet} = orm;
const entityData = res.locals.entity;
try {
const relationshipSet = await RelationshipSet.forge({id: entityData.relationshipSetId})
.fetch({
require: false,
withRelated: [
'relationships.source',
'relationships.target',
'relationships.type'
]
});
res.locals.relationships = relationshipSet ? relationshipSet.related('relationships').toJSON() : [];

return next();
}
catch (error) {
// What do we do with the error here?
return next(error);
}
};
}

export function formatQueryParameters() {
return (req, res, next) => {
req.query = _.mapKeys(req.query, (value, key) => _.toLower(key));
return next();
};
}
58 changes: 58 additions & 0 deletions src/api/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

import _ from 'lodash';
import {loadEntity} from './middleware';


export const aliasesRelations = ['aliasSet.aliases.language'];
export const identifiersRelations = ['identifierSet.identifiers.type'];
export const relationshipsRelations = ['relationshipSet.relationships.type'];
Expand All @@ -40,3 +44,57 @@ export function allowOnlyGetMethod(req, res, next) {
.status(405)
.send({message: `${req.method} method for the "${req.path}" route is not supported. Only GET method is allowed`});
}


export async function getBrowsedRelationships(orm, locals, browsedEntityType,
getEntityInfoMethod, fetchRelated, filterRelationshipMethod) {
const {entity, relationships} = locals;

if (!relationships.length > 0) {
return [];
}
const relationshipsPromises = relationships
.map(async relationship => {
let relEntity;
if (entity.bbid === relationship.sourceBbid &&
relationship.target.type.toLowerCase() === browsedEntityType.toLowerCase()) {
relEntity = relationship.target;
}
else if (relationship.source.type.toLowerCase() === browsedEntityType.toLowerCase()) {
relEntity = relationship.source;
}

if (relEntity) {
const loadedRelEntity = await loadEntity(orm, relEntity, fetchRelated);
const formattedRelEntity = getEntityInfoMethod(loadedRelEntity);
if (!filterRelationshipMethod(formattedRelEntity)) {
return null;
}
return {
entity: formattedRelEntity,
relationship: [{
relationshipType: _.get(relationship, 'type.label', null),
relationshipTypeID: _.get(relationship, 'type.id', null)
}]
};
}
return null;
});
const fetchedRelationshipsPromises = await Promise.all(relationshipsPromises);
// Remove falsy values (nulls returned above)
const filteredRelationships = fetchedRelationshipsPromises.filter(Boolean);

const flattenedRelationships = filteredRelationships
.reduce((accumulator, relationship, index, array) => {
const entityAlreadyExists = accumulator.find(rel => rel.entity.bbid === relationship.entity.bbid);
if (entityAlreadyExists) {
entityAlreadyExists.relationships.push(...relationship.relationships);
}
else {
accumulator.push(relationship);
}
return accumulator;
}, []);

return flattenedRelationships;
}
9 changes: 5 additions & 4 deletions src/api/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ import workRouter from './routes/work';
* properties:
* name:
* type: string
* example: 'Robert A. Heinlein'
* example: '<Name of entity>'
* sortName:
* type: string
* example: 'Heinlein, Robert A.'
* aliasLanguage:
* example: '<Sort name of entity>'
* language:
* type: string
* example: 'English'
* example: '<Alias language>'
* description: Three letter ISO 639-3 language code
* primary:
* type: boolean
* example: true
Expand Down
Loading