Skip to content

Commit

Permalink
Publishable minimum write capabilities
Browse files Browse the repository at this point in the history
  • Loading branch information
lmd59 committed Jun 4, 2024
1 parent 873349f commit 975a9ab
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 24 deletions.
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"test:service": "npm run test --workspace=service"
},
"devDependencies": {
"@types/lodash": "^4.17.4",
"concurrently": "^7.6.0"
}
}
3 changes: 2 additions & 1 deletion service/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
PORT=3000
HOST='localhost'
DATABASE_URL='mongodb://localhost:27017/measure-repository?replicaSet=rs0'
VSAC_API_KEY="<your-api-key>" # Add if you plan on using the `include-terminology` query param
VSAC_API_KEY="<your-api-key>" # Add if you plan on using the `include-terminology` query param
AUTHORING=true # Make false if this is running as a publishable repository instead
22 changes: 21 additions & 1 deletion service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,28 @@ This server currently supports the following CRUD operations:

- Read by ID with `GET` to endpoint: `4_0_1/<resourceType>/<resourceId>`
- Create resource (Library or Measure) with `POST` to endpoint: `4_0_1/<resourceType>`
- Publishable:
- Supports the [CRMI Publishable Artifact Repository](https://hl7.org/fhir/uv/crmi/1.0.0-snapshot/artifact-repository-service.html#publishable-artifact-repository) minimum write capability _publish_
- Artifact must be in active status and conform to appropriate shareable and publishable profiles
- Authoring (In Progress):
- Supports the [CRMI Authoring Artifact Repository](https://hl7.org/fhir/uv/crmi/1.0.0-snapshot/artifact-repository-service.html#authoring-artifact-repository) additional authoring capability _submit_
- Artifact must be in draft status

- Update resource (Library or Measure) with `PUT` to endpoint: `4_0_1/<resourceType>/<resourceId>`
_More functionality coming soon!_
- Publishable:
- Supports the [CRMI Publishable Artifact Repository](https://hl7.org/fhir/uv/crmi/1.0.0-snapshot/artifact-repository-service.html#publishable-artifact-repository) minimum write capability _retire_
- Artifact must be in active status and may only change the status to retired and update the date (and other metadata appropriate to indicate retired status)
- Authoring (In Progress):
- Supports the [CRMI Authoring Artifact Repository](https://hl7.org/fhir/uv/crmi/1.0.0-snapshot/artifact-repository-service.html#authoring-artifact-repository) additional authoring capability _revise_
- Artifact must be in (and remain in) draft status

- Delete resource (Library or Measure) with `DELETE` to endpoint: `4_0_1/<resourceType>/<resourceId>`
- Publishable:
- Supports the [CRMI Publishable Artifact Repository](https://hl7.org/fhir/uv/crmi/1.0.0-snapshot/artifact-repository-service.html#publishable-artifact-repository) minimum write capability _archive_
- Artifact must be in retired status
- Authoring (In Progress):
- Supports the [CRMI Authoring Artifact Repository](https://hl7.org/fhir/uv/crmi/1.0.0-snapshot/artifact-repository-service.html#authoring-artifact-repository) additional authoring capability _withdraw_
- Artifact must be in draft status

### Search

Expand Down
13 changes: 13 additions & 0 deletions service/src/db/dbOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,16 @@ export async function updateResource(id: string, data: fhir4.FhirResource, resou

return { id, created: false };
}

/**
* Searches for a document for a resource and deletes it if found
*/
export async function deleteResource(id: string, resourceType: string) {
const collection = Connection.db.collection(resourceType);
logger.debug(`Finding and deleting ${resourceType}/${id} from database`);
const results = await collection.deleteOne({ id });
if (results.deletedCount === 1) {
return { id, deleted: true };
}
return { id, deleted: false };
}
32 changes: 21 additions & 11 deletions service/src/services/LibraryService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { loggers, RequestArgs, RequestCtx } from '@projecttacoma/node-fhir-server-core';
import {
createResource,
deleteResource,
findDataRequirementsWithQuery,
findResourceById,
findResourceCountWithQuery,
Expand All @@ -18,7 +19,10 @@ import {
gatherParams,
validateParamIdSource,
checkContentTypeHeader,
checkExpectedResourceType
checkExpectedResourceType,
updateFields,
checkFieldsforUpdate,
checkFieldsForDelete
} from '../util/inputUtils';
import { v4 as uuidv4 } from 'uuid';
import { Calculator } from 'fqm-execution';
Expand Down Expand Up @@ -97,18 +101,13 @@ export class LibraryService implements Service<fhir4.Library> {
checkContentTypeHeader(contentType);
const resource = req.body;
checkExpectedResourceType(resource.resourceType, 'Library');
resource['id'] = uuidv4();
if (resource.status != 'active') {
resource.status = 'active';
logger.warn(`Resource ${resource.id} has been coerced to active`);
}
updateFields(resource);
return createResource(resource, 'Library');
}

/**
* result of sending a PUT request to {BASE_URL}/4_0_1/Library/{id}
* updates the library with the passed in id using the passed in data
* or creates a library with passed in id if it does not exist in the database
*/
async update(args: RequestArgs, { req }: RequestCtx) {
logger.info(`PUT /Library/${args.id}`);
Expand All @@ -120,13 +119,24 @@ export class LibraryService implements Service<fhir4.Library> {
if (resource.id !== args.id) {
throw new BadRequestError('Argument id must match request body id for PUT request');
}
if (resource.status != 'active') {
resource.status = 'active';
logger.warn(`Resource ${resource.id} has been coerced to active`);
}
const oldResource = (await findResourceById(resource.id, resource.resourceType)) as fhir4.Library | null;
checkFieldsforUpdate(resource, oldResource);
return updateResource(args.id, resource, 'Library');
}

/**
* result of sending a DELETE request to {BASE_URL}/4_0_1/Library/{id}
* deletes the library with the passed in id if it exists in the database
*/
async delete(args: RequestArgs, { req }: RequestCtx) {
const resource = (await findResourceById(args.id, 'Library')) as fhir4.Library | null;
if (!resource) {
throw new ResourceNotFoundError(`Existing resource not found with id ${args.id}`);
}
checkFieldsForDelete(resource);
return deleteResource(args.id, 'Library');
}

/**
* result of sending a POST or GET request to:
* {BASE_URL}/4_0_1/Library/$cqfm.package or {BASE_URL}/4_0_1/Library/:id/$cqfm.package
Expand Down
31 changes: 21 additions & 10 deletions service/src/services/MeasureService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { loggers, RequestArgs, RequestCtx } from '@projecttacoma/node-fhir-server-core';
import {
createResource,
deleteResource,
findDataRequirementsWithQuery,
findResourceById,
findResourceCountWithQuery,
Expand All @@ -17,7 +18,10 @@ import {
gatherParams,
validateParamIdSource,
checkContentTypeHeader,
checkExpectedResourceType
checkExpectedResourceType,
updateFields,
checkFieldsforUpdate,
checkFieldsForDelete
} from '../util/inputUtils';
import { Calculator } from 'fqm-execution';
import { MeasureSearchArgs, MeasureDataRequirementsArgs, PackageArgs, parseRequestSchema } from '../requestSchemas';
Expand Down Expand Up @@ -98,11 +102,7 @@ export class MeasureService implements Service<fhir4.Measure> {
checkContentTypeHeader(contentType);
const resource = req.body;
checkExpectedResourceType(resource.resourceType, 'Measure');
resource['id'] = uuidv4();
if (resource.status != 'active') {
resource.status = 'active';
logger.warn(`Resource ${resource.id} has been coerced to active`);
}
updateFields(resource);
return createResource(resource, 'Measure');
}

Expand All @@ -121,13 +121,24 @@ export class MeasureService implements Service<fhir4.Measure> {
if (resource.id !== args.id) {
throw new BadRequestError('Argument id must match request body id for PUT request');
}
if (resource.status != 'active') {
resource.status = 'active';
logger.warn(`Resource ${resource.id} has been coerced to active`);
}
const oldResource = (await findResourceById(resource.id, resource.resourceType)) as fhir4.Measure | null;
checkFieldsforUpdate(resource, oldResource);
return updateResource(args.id, resource, 'Measure');
}

/**
* result of sending a DELETE request to {BASE_URL}/4_0_1/Library/{id}
* deletes the library with the passed in id if it exists in the database
*/
async delete(args: RequestArgs, { req }: RequestCtx) {
const resource = (await findResourceById(args.id, 'Measure')) as fhir4.Measure | null;
if (!resource) {
throw new ResourceNotFoundError(`Existing resource not found with id ${args.id}`);
}
checkFieldsForDelete(resource);
return deleteResource(args.id, 'Measure');
}

/**
* result of sending a POST or GET request to:
* {BASE_URL}/4_0_1/Measure/$cqfm.package or {BASE_URL}/4_0_1/Measure/:id/$cqfm.package
Expand Down
58 changes: 57 additions & 1 deletion service/src/util/inputUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { RequestArgs, RequestQuery, FhirResourceType } from '@projecttacoma/node-fhir-server-core';
import { Filter } from 'mongodb';
import { BadRequestError } from './errorUtils';
import { BadRequestError, ResourceNotFoundError } from './errorUtils';
import { v4 as uuidv4 } from 'uuid';
import _ from 'lodash';
import { loggers } from '@projecttacoma/node-fhir-server-core';
const logger = loggers.get('default');

/*
* Gathers parameters from both the query and the FHIR parameter request body resource
Expand Down Expand Up @@ -67,3 +71,55 @@ export function checkExpectedResourceType(resourceType: string, expectedResource
throw new BadRequestError(`Expected resourceType '${expectedResourceType}' in body. Received '${resourceType}'.`);
}
}

export function updateFields(resource: fhir4.Measure | fhir4.Library) {
resource['id'] = uuidv4();
if (process.env.AUTHORING) {
// authoring requires active or draft, TODO: return error instead
if (resource.status !== 'active' && resource.status !== 'draft') {
resource.status = 'active';
logger.warn(`Resource ${resource.id} has been coerced to active`);
}
} else {
// publishable requires active, TODO: return error instead
if (resource.status !== 'active') {
resource.status = 'active';
logger.warn(`Resource ${resource.id} has been coerced to active`);
}
}
}

export function checkFieldsforUpdate(
resource: fhir4.Measure | fhir4.Library,
oldResource: fhir4.Measure | fhir4.Library | null
) {
if (!oldResource) {
throw new ResourceNotFoundError(`Existing resource not found with id ${resource.id}`);
}
if (!process.env.AUTHORING || oldResource.status === 'active') {
// publishable or active status requires retire functionality
// TODO: is there any other metadata we should allow to update for the retire functionality?
if (!process.env.AUTHORING && oldResource.status !== 'active') {
throw new BadRequestError(
`Resource status is currently ${oldResource.status}. Publishable repository service updates may only be made to active status resources.`
);
}
const { status: statusOld, date: dateOld, ...limitedOld } = oldResource;
const { status: statusNew, date: dateNew, ...limitedNew } = resource;

if (statusNew !== 'retired') {
throw new BadRequestError('Updating active status resources requires changing the resource status to retired.');
}

if (!_.isEqual(limitedOld, limitedNew)) {
throw new BadRequestError('Updating active status resources may only change the status and date.');
}
} else if (oldResource.status === 'draft') {
// authoring and draft status requires revise functionality
if (resource.status != 'draft') {
throw new BadRequestError('Existing draft resources must stay in draft while revising.');
}
} else {
throw new BadRequestError(`Cannot update existing resource with status ${oldResource.status}`);
}
}

0 comments on commit 975a9ab

Please sign in to comment.