diff --git a/service/src/db/dbOperations.ts b/service/src/db/dbOperations.ts index d538b8c..906b9dc 100644 --- a/service/src/db/dbOperations.ts +++ b/service/src/db/dbOperations.ts @@ -20,7 +20,7 @@ export async function findResourcesWithQuery( resourceType: FhirResourceType ) { const collection = Connection.db.collection(resourceType); - const projection = { 'data._id': 0, 'data._dataRequirements': 0 }; + const projection = { _id: 0, _dataRequirements: 0 }; const pagination: any = query.skip ? [{ $skip: query.skip }, { $limit: query.limit }] : []; query._dataRequirements = { $exists: false }; query._summary = { $exists: false }; @@ -31,13 +31,13 @@ export async function findResourcesWithQuery( return collection .aggregate<{ metadata: { total: number }[]; data: T }>([ { $match: query }, + { $project: projection }, { $facet: { metadata: [{ $count: 'total' }], data: pagination } - }, - { $project: projection } + } ]) .toArray(); } @@ -46,7 +46,7 @@ export async function findResourcesWithQuery( * searches the database and returns an array objects that contain metadata and each respective resource of the given type that match the given query * but the resources only include the elements specified by the _elements parameter */ -export async function findResourceElementsWithQuery( +export async function findResourceElementsWithQuery( query: Filter, resourceType: FhirResourceType ) { @@ -57,7 +57,7 @@ export async function findResourceElementsWithQuery { - projection[elem] = 1; + projection[`${elem}`] = 1; }); projection['_id'] = 0; @@ -72,13 +72,13 @@ export async function findResourceElementsWithQuery([ { $match: query }, + { $project: projection }, { $facet: { metadata: [{ $count: 'total' }], data: pagination } - }, - { $project: projection } + } ]) .toArray(); } @@ -93,6 +93,8 @@ export async function findResourceCountWithQuery(query: Filter, resourceTyp query._dataRequirements = { $exists: false }; query._summary = { $exists: false }; query._elements = { $exists: false }; + query.limit = { $exists: false }; + query.skip = { $exists: false }; return collection.countDocuments(query); } diff --git a/service/src/services/LibraryService.ts b/service/src/services/LibraryService.ts index 39ae822..b9daecf 100644 --- a/service/src/services/LibraryService.ts +++ b/service/src/services/LibraryService.ts @@ -61,8 +61,8 @@ export class LibraryService implements Service { // then return a searchset bundle that includes only those elements // on those resource entries else if (parsedQuery._elements) { - const result = await findResourceElementsWithQuery(mongoQuery, 'Library'); - const entries = result.map(r => r.data); + const result = await findResourceElementsWithQuery(mongoQuery, 'Library'); + const entries = result[0].data; // add the SUBSETTED tag to the resources returned by the _elements parameter entries.map(e => { if (e.meta) { diff --git a/service/src/services/MeasureService.ts b/service/src/services/MeasureService.ts index 4f0d55f..0a7dc95 100644 --- a/service/src/services/MeasureService.ts +++ b/service/src/services/MeasureService.ts @@ -62,8 +62,8 @@ export class MeasureService implements Service { // then return a searchset bundle that includes only those elements // on those resource entries else if (parsedQuery._elements) { - const result = await findResourceElementsWithQuery(mongoQuery, 'Measure'); - const entries = result.map(r => r.data); + const result = await findResourceElementsWithQuery(mongoQuery, 'Measure'); + const entries = result[0].data; // add the SUBSETTED tag to the resources returned by the _elements parameter entries.map(e => { if (e.meta) { diff --git a/service/src/util/queryUtils.ts b/service/src/util/queryUtils.ts index e292788..6d5464c 100644 --- a/service/src/util/queryUtils.ts +++ b/service/src/util/queryUtils.ts @@ -8,43 +8,38 @@ const STRING_TYPE_PARAMS = ['name', 'title', 'description', 'version']; * a usable mongo query */ export function getMongoQueryFromRequest(query: RequestQuery): Filter { - const pageSize = (query['_count'] && parseInt(query['_count'] as string)) || 100; // set a base limit of 100 - if (!query['page']) query['page'] = '1'; // default to first page - - //TODO: Handle potential for query value to be array - return Object.keys(query).reduce((mf: Filter, key: string) => { + const pageSize = parseInt((query['_count'] || '50') as string); //default to limit 50 + const filter: Filter = { limit: pageSize, skip: (parseInt((query['page'] || '1') as string) - 1) * pageSize }; //default to first page + Object.keys(query).forEach((key: string) => { + //TODO: Handle potential for query value to be array if (!query[key] || Array.isArray(query[key])) { throw new NotImplementedError( `Retrieved undefined or multiple arguments for query param: ${key}. Multiple arguments for the same query param and undefined arguments are not supported.` ); } else if (STRING_TYPE_PARAMS.includes(key)) { // For string arguments in query they may match just the start of a string and are case insensitive - mf[key] = { $regex: `^${query[key]}`, $options: 'i' }; + filter[key] = { $regex: `^${query[key]}`, $options: 'i' }; } else if (key === 'identifier') { // Identifier can check against the identifier.system, identifier.value, or both on a resource const iden = query.identifier as string; const splitIden = iden.split('|'); if (splitIden.length === 1) { - mf['identifier.value'] = splitIden[0]; + filter['identifier.value'] = splitIden[0]; } else if (splitIden[0] === '') { - mf['identifier.value'] = splitIden[1]; + filter['identifier.value'] = splitIden[1]; } else if (splitIden[1] === '') { - mf['identifier.system'] = splitIden[0]; + filter['identifier.system'] = splitIden[0]; } else { - mf['identifier.system'] = splitIden[0]; - mf['identifier.value'] = splitIden[1]; + filter['identifier.system'] = splitIden[0]; + filter['identifier.value'] = splitIden[1]; } } else if (key === '_elements') { const elements = query[key] as string; - mf[key] = elements.split(','); - } else if (key === '_count') { - mf['limit'] = pageSize; - } else if (key === 'page') { - mf['skip'] = (parseInt((query[key] || '1') as string) - 1) * pageSize; //default to first page - } else { - // Otherwise no parsing necessary - mf[key] = query[key]; + filter[key] = elements.split(','); + } else if (key !== '_count' && key !== 'page') { + // Skip _count and page, otherwise no parsing necessary + filter[key] = query[key]; } - return mf; - }, {}); + }); + return filter; } diff --git a/service/test/services/LibraryService.test.ts b/service/test/services/LibraryService.test.ts index 8f3d01b..16a6c1c 100644 --- a/service/test/services/LibraryService.test.ts +++ b/service/test/services/LibraryService.test.ts @@ -434,7 +434,7 @@ describe('LibraryService', () => { .then(response => { expect(response.body.issue[0].code).toEqual('invalid'); expect(response.body.issue[0].details.text).toEqual( - 'Multiple resources found in collection: Library, with identifier: http://example.com/libraryWithSameSystem| and page: 1. /Library/$cqfm.package operation must specify a single Library' + 'Multiple resources found in collection: Library, with identifier: http://example.com/libraryWithSameSystem|. /Library/$cqfm.package operation must specify a single Library' ); }); }); @@ -496,7 +496,7 @@ describe('LibraryService', () => { .then(response => { expect(response.body.issue[0].code).toEqual('not-found'); expect(response.body.issue[0].details.text).toEqual( - 'No resource found in collection: Library, with id: testLibraryWithDeps and url: http://example.com/invalid and page: 1' + 'No resource found in collection: Library, with id: testLibraryWithDeps and url: http://example.com/invalid' ); }); }); @@ -516,7 +516,7 @@ describe('LibraryService', () => { .then(response => { expect(response.body.issue[0].code).toEqual('not-found'); expect(response.body.issue[0].details.text).toEqual( - 'No resource found in collection: Library, with id: invalid and url: http://example.com/invalid and page: 1' + 'No resource found in collection: Library, with id: invalid and url: http://example.com/invalid' ); }); }); diff --git a/service/test/services/MeasureService.test.ts b/service/test/services/MeasureService.test.ts index 5000043..4d0f501 100644 --- a/service/test/services/MeasureService.test.ts +++ b/service/test/services/MeasureService.test.ts @@ -437,7 +437,7 @@ describe('MeasureService', () => { .then(response => { expect(response.body.issue[0].code).toEqual('not-found'); expect(response.body.issue[0].details.text).toEqual( - 'No resource found in collection: Measure, with id: testWithUrl and url: http://example.com/invalid and page: 1' + 'No resource found in collection: Measure, with id: testWithUrl and url: http://example.com/invalid' ); }); }); @@ -499,7 +499,7 @@ describe('MeasureService', () => { .then(response => { expect(response.body.issue[0].code).toEqual('not-found'); expect(response.body.issue[0].details.text).toEqual( - 'No resource found in collection: Measure, with id: invalid and url: http://example.com/invalid and page: 1' + 'No resource found in collection: Measure, with id: invalid and url: http://example.com/invalid' ); }); }); diff --git a/service/test/util/queryUtils.test.ts b/service/test/util/queryUtils.test.ts index a16daf7..8f616bf 100644 --- a/service/test/util/queryUtils.test.ts +++ b/service/test/util/queryUtils.test.ts @@ -8,16 +8,17 @@ const QUERY_WITH_SYSTEM_AND_CODE_IDEN = { url: 'http://example.com', identifier: const QUERY_WITH_STRING_PARAM = { url: 'http://example.com', version: 'test' }; const QUERY_WITH_MULTIPLE_STRING_PARAM = { url: 'http://example.com', version: ['test', '...anotherTest?'] }; -const EXPECTED_QUERY_WITH_NO_IDEN = { skip: 0, url: 'http://example.com' }; -const EXPECTED_QUERY_WITH_CODE_IDEN = { skip: 0, url: 'http://example.com', 'identifier.value': 'testCode' }; -const EXPECTED_QUERY_WITH_SYSTEM_IDEN = { skip: 0, url: 'http://example.com', 'identifier.system': 'testSystem' }; +const EXPECTED_QUERY_WITH_NO_IDEN = { limit: 50, skip: 0, url: 'http://example.com' }; +const EXPECTED_QUERY_WITH_CODE_IDEN = { limit: 50, skip: 0, url: 'http://example.com', 'identifier.value': 'testCode' }; +const EXPECTED_QUERY_WITH_SYSTEM_IDEN = { limit: 50, skip: 0, url: 'http://example.com', 'identifier.system': 'testSystem' }; const EXPECTED_QUERY_WITH_SYSTEM_AND_CODE_IDEN = { + limit: 50, skip: 0, url: 'http://example.com', 'identifier.system': 'testSystem', 'identifier.value': 'testCode' }; -const EXPECTED_QUERY_WITH_STRING_PARAM = { skip: 0, url: 'http://example.com', version: { $regex: '^test', $options: 'i' } }; +const EXPECTED_QUERY_WITH_STRING_PARAM = { limit: 50, skip: 0, url: 'http://example.com', version: { $regex: '^test', $options: 'i' } }; describe('getMongoQueryFromRequest', () => { it('correctly parses a query with no identifier field', () => {