Skip to content

Commit

Permalink
Merge branch 'dev' into UserRoleTesting
Browse files Browse the repository at this point in the history
  • Loading branch information
al-rosenthal authored Sep 8, 2023
2 parents bc0a4af + 5d80722 commit 1300f4c
Show file tree
Hide file tree
Showing 71 changed files with 4,169 additions and 118 deletions.
6 changes: 6 additions & 0 deletions api/.pipeline/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ const phases = {
backboneIntakePath: '/api/dwc/submission/queue',
backboneArtifactIntakePath: '/api/artifact/intake',
backboneIntakeEnabled: false,
bctwApiHost: 'https://moe-bctw-api-dev.apps.silver.devops.gov.bc.ca',
critterbaseApiHost: 'https://moe-critterbase-api-dev.apps.silver.devops.gov.bc.ca/api',
env: 'dev',
elasticsearchURL: 'http://es01.a0ec71-dev:9200',
elasticsearchTaxonomyIndex: 'taxonomy_3.0.0',
Expand Down Expand Up @@ -113,6 +115,8 @@ const phases = {
backboneIntakePath: '/api/dwc/submission/queue',
backboneArtifactIntakePath: '/api/artifact/intake',
backboneIntakeEnabled: false,
bctwApiHost: 'https://moe-bctw-api-test.apps.silver.devops.gov.bc.ca',
critterbaseApiHost: 'https://moe-critterbase-api-test.apps.silver.devops.gov.bc.ca/api',
env: 'test',
elasticsearchURL: 'http://es01.a0ec71-dev:9200',
elasticsearchTaxonomyIndex: 'taxonomy_3.0.0',
Expand Down Expand Up @@ -143,6 +147,8 @@ const phases = {
backboneIntakePath: '/api/dwc/submission/queue',
backboneArtifactIntakePath: '/api/artifact/intake',
backboneIntakeEnabled: false,
bctwApiHost: 'https://moe-bctw-api-prod.apps.silver.devops.gov.bc.ca',
critterbaseApiHost: 'https://moe-critterbase-api-prod.apps.silver.devops.gov.bc.ca/api',
env: 'prod',
elasticsearchURL: 'http://es01.a0ec71-prod:9200',
elasticsearchTaxonomyIndex: 'taxonomy_3.0.0',
Expand Down
2 changes: 2 additions & 0 deletions api/.pipeline/lib/api.deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const apiDeploy = async (settings) => {
BACKBONE_INTAKE_PATH: phases[phase].backboneIntakePath,
BACKBONE_ARTIFACT_INTAKE_PATH: phases[phase].backboneArtifactIntakePath,
BACKBONE_INTAKE_ENABLED: phases[phase].backboneIntakeEnabled,
BCTW_API_HOST: phases[phase].bctwApiHost,
CB_API_HOST: phases[phase].critterbaseApiHost,
NODE_ENV: phases[phase].env || 'dev',
ELASTICSEARCH_URL: phases[phase].elasticsearchURL,
ELASTICSEARCH_TAXONOMY_INDEX: phases[phase].elasticsearchTaxonomyIndex,
Expand Down
10 changes: 10 additions & 0 deletions api/.pipeline/templates/api.dc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ parameters:
- name: BACKBONE_API_HOST
required: true
description: API host for BioHub Platform Backbone. Example "https://platform.com".
- name: CB_API_HOST
required: true
description: API host for the Critterbase service, SIMS API will hit this to retrieve critter metadata. Example "https://critterbase.com".
- name: BCTW_API_HOST
required: true
description: API host for the BC Telemetry Warehouse service. SIMS API will hit this for device deployments and other telemetry operations. Example "https://bctw.com".
- name: BACKBONE_INTAKE_PATH
required: true
description: API path for BioHub Platform Backbone DwCA submission intake endpoint. Example "/api/path/to/intake".
Expand Down Expand Up @@ -190,6 +196,10 @@ objects:
value: ${APP_HOST}
- name: BACKBONE_API_HOST
value: ${BACKBONE_API_HOST}
- name: CB_API_HOST
value: ${CB_API_HOST}
- name: BCTW_API_HOST
value: ${BCTW_API_HOST}
- name: BACKBONE_INTAKE_PATH
value: ${BACKBONE_INTAKE_PATH}
- name: BACKBONE_ARTIFACT_INTAKE_PATH
Expand Down
3 changes: 3 additions & 0 deletions api/src/models/survey-create.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Feature } from 'geojson';
import { PostSurveyBlock } from '../repositories/survey-block-repository';

export class PostSurveyObject {
survey_details: PostSurveyDetailsData;
Expand All @@ -11,6 +12,7 @@ export class PostSurveyObject {
agreements: PostAgreementsData;
participants: PostParticipationData[];
partnerships: PostPartnershipsData;
blocks: PostSurveyBlock[];

constructor(obj?: any) {
this.survey_details = (obj?.survey_details && new PostSurveyDetailsData(obj.survey_details)) || null;
Expand All @@ -26,6 +28,7 @@ export class PostSurveyObject {
this.participants =
(obj?.participants?.length && obj.participants.map((p: any) => new PostParticipationData(p))) || [];
this.partnerships = (obj?.partnerships && new PostPartnershipsData(obj.partnerships)) || null;
this.blocks = (obj?.blocks && obj.blocks.map((p: any) => p as PostSurveyBlock)) || [];
}
}

Expand Down
3 changes: 3 additions & 0 deletions api/src/models/survey-update.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Feature } from 'geojson';
import { PostSurveyBlock } from '../repositories/survey-block-repository';

export class PutSurveyObject {
survey_details: PutSurveyDetailsData;
Expand All @@ -10,6 +11,7 @@ export class PutSurveyObject {
location: PutSurveyLocationData;
participants: PutSurveyParticipantsData[];
partnerships: PutPartnershipsData;
blocks: PostSurveyBlock[];

constructor(obj?: any) {
this.survey_details = (obj?.survey_details && new PutSurveyDetailsData(obj.survey_details)) || null;
Expand All @@ -24,6 +26,7 @@ export class PutSurveyObject {
this.participants =
(obj?.participants?.length && obj.participants.map((p: any) => new PutSurveyParticipantsData(p))) || [];
this.partnerships = (obj?.partnerships && new PutPartnershipsData(obj.partnerships)) || null;
this.blocks = (obj?.blocks && obj.blocks.map((p: any) => p as PostSurveyBlock)) || [];
}
}

Expand Down
2 changes: 2 additions & 0 deletions api/src/models/survey-view.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Feature } from 'geojson';
import { SurveyMetadataPublish } from '../repositories/history-publish-repository';
import { IPermitModel } from '../repositories/permit-repository';
import { SurveyBlockRecord } from '../repositories/survey-block-repository';
import { SurveyUser } from '../repositories/survey-participation-repository';

export type SurveyObject = {
Expand All @@ -13,6 +14,7 @@ export type SurveyObject = {
location: GetSurveyLocationData;
participants: SurveyUser[];
partnerships: ISurveyPartnerships;
blocks: SurveyBlockRecord[];
};

export interface ISurveyPartnerships {
Expand Down
64 changes: 64 additions & 0 deletions api/src/paths/critter-data/critters/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import Ajv from 'ajv';
import chai, { expect } from 'chai';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { CritterbaseService, IBulkCreate } from '../../../services/critterbase-service';
import { getRequestHandlerMocks } from '../../../__mocks__/db';
import * as createCritter from './index';

chai.use(sinonChai);

describe('paths/critter-data/critters/post', () => {
const ajv = new Ajv();

it('is valid openapi v3 schema', () => {
expect(ajv.validateSchema((createCritter.POST.apiDoc as unknown) as object)).to.be.true;
});

const payload: IBulkCreate = {
critters: [],
captures: [],
mortalities: [],
locations: [],
markings: [],
qualitative_measurements: [],
quantitative_measurements: [],
families: [],
collections: []
};

describe('createCritter', () => {
afterEach(() => {
sinon.restore();
});
it('should succeed', async () => {
const mockCreateCritter = sinon.stub(CritterbaseService.prototype, 'createCritter').resolves({ count: 0 });
const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.body = payload;
const requestHandler = createCritter.createCritter();

await requestHandler(mockReq, mockRes, mockNext);

expect(mockCreateCritter).to.have.been.calledOnceWith(payload);
//expect(mockCreateCritter).calledWith(payload);
expect(mockRes.statusValue).to.equal(201);
expect(mockRes.json.calledWith({ count: 0 })).to.be.true;
});
it('should fail', async () => {
const mockError = new Error('mock error');
const mockCreateCritter = sinon.stub(CritterbaseService.prototype, 'createCritter').rejects(mockError);

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.body = payload;
const requestHandler = createCritter.createCritter();

try {
await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect(actualError).to.equal(mockError);
expect(mockCreateCritter).to.have.been.calledOnceWith(payload);
}
});
});
});
160 changes: 160 additions & 0 deletions api/src/paths/critter-data/critters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { RequestHandler } from 'express';
import { Operation } from 'express-openapi';
import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../constants/roles';
import { authorizeRequestHandler } from '../../../request-handlers/security/authorization';
import { CritterbaseService, ICritterbaseUser } from '../../../services/critterbase-service';
import { getLogger } from '../../../utils/logger';

const defaultLog = getLogger('paths/critter-data/critters');
export const POST: Operation = [
authorizeRequestHandler((req) => {
return {
or: [
{
validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR],
projectId: Number(req.params.projectId),
discriminator: 'ProjectPermission'
},
{
validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR],
discriminator: 'SystemRole'
}
]
};
}),
createCritter()
];

POST.apiDoc = {
description:
'Creates a new critter in critterbase. Optionally make captures, markings, measurements, etc. along with it.',
tags: ['critterbase'],
security: [
{
Bearer: []
}
],
requestBody: {
description: 'Critterbase bulk creation request object',
content: {
'application/json': {
schema: {
title: 'Bulk post request object',
type: 'object',
properties: {
critters: {
title: 'critters',
type: 'array',
items: {
title: 'critter',
type: 'object'
}
},
captures: {
title: 'captures',
type: 'array',
items: {
title: 'capture',
type: 'object'
}
},
collections: {
title: 'collection units',
type: 'array',
items: {
title: 'collection unit',
type: 'object'
}
},
markings: {
title: 'markings',
type: 'array',
items: {
title: 'marking',
type: 'object'
}
},
locations: {
title: 'locations',
type: 'array',
items: {
title: 'location',
type: 'object'
}
},
mortalities: {
title: 'locations',
type: 'array',
items: {
title: 'location',
type: 'object'
}
},
qualitative_measurements: {
title: 'qualitative measurements',
type: 'array',
items: {
title: 'qualitative measurement',
type: 'object'
}
},
quantitative_measurements: {
title: 'quantitative measurements',
type: 'array',
items: {
title: 'quantitative measurement',
type: 'object'
}
}
}
}
}
}
},
responses: {
201: {
description: 'Responds with counts of objects created in critterbase.',
content: {
'application/json': {
schema: {
title: 'Bulk creation response object',
type: 'object'
}
}
}
},
400: {
$ref: '#/components/responses/400'
},
401: {
$ref: '#/components/responses/401'
},
403: {
$ref: '#/components/responses/401'
},
500: {
$ref: '#/components/responses/500'
},
default: {
$ref: '#/components/responses/default'
}
}
};

export function createCritter(): RequestHandler {
return async (req, res) => {
const user: ICritterbaseUser = {
keycloak_guid: req['system_user']?.user_guid,
username: req['system_user']?.user_identifier
};

const cb = new CritterbaseService(user);
try {
const result = await cb.createCritter(req.body);
return res.status(201).json(result);
} catch (error) {
defaultLog.error({ label: 'createCritter', message: 'error', error });
throw error;
}
};
}
Loading

0 comments on commit 1300f4c

Please sign in to comment.