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

Display survey occurrences #417

Merged
merged 13 commits into from
Jul 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 29 additions & 0 deletions api/src/models/template-observations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { getLogger } from '../utils/logger';

const defaultLog = getLogger('models/template-observations');

/**
* Pre-processes GET project/survey template observations data
*
* @export
* @class GetTemplateObservationsData
*/
export class GetTemplateObservationsData {
templateObservationsList: any[];

constructor(templateObservationsData?: any) {
defaultLog.debug({ label: 'GetTemplateObservationsData', message: 'params', templateObservationsData });

this.templateObservationsList =
(templateObservationsData?.length &&
templateObservationsData.map((item: any) => {
return {
id: item.id,
fileName: item.key,
lastModified: item.update_date || item.create_date,
size: item.file_size
};
})) ||
[];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export function uploadMedia(): RequestHandler {
const key = generateS3FileKey({
projectId: Number(req.params.projectId),
surveyId: Number(req.params.surveyId),
folder: 'template',
fileName: rawMediaFile.originalname
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
'use strict';

import { RequestHandler } from 'express';
import { Operation } from 'express-openapi';
import { HTTP400 } from '../../../../../../../errors/CustomError';
import { getLogger } from '../../../../../../../utils/logger';
import { getDBConnection } from '../../../../../../../database/db';
import { getSurveyTemplateS3KeySQL } from '../../../../../../../queries/survey/survey-occurrence-queries';
import { getS3SignedURL } from '../../../../../../../utils/file-utils';
import { attachmentApiDocObject } from '../../../../../../../utils/shared-api-docs';

const defaultLog = getLogger('/api/project/{projectId}/survey/{surveyId}/template/{templateId}/getSignedUrl');

export const GET: Operation = [getSingleTemplateURL()];

GET.apiDoc = {
...attachmentApiDocObject(
'Retrieves the signed url of template observation in a survey by its template id.',
'GET response containing the signed url of a template.'
),
parameters: [
{
in: 'path',
name: 'projectId',
schema: {
type: 'number'
},
required: true
},
{
in: 'path',
name: 'surveyId',
schema: {
type: 'number'
},
required: true
},
{
in: 'path',
name: 'templateId',
schema: {
type: 'number'
},
required: true
}
]
};

export function getSingleTemplateURL(): RequestHandler {
return async (req, res) => {
defaultLog.debug({ label: 'Get single template url', message: 'params', req_params: req.params });

if (!req.params.surveyId) {
throw new HTTP400('Missing required path param `surveyId`');
}

if (!req.params.templateId) {
throw new HTTP400('Missing required path param `templateId`');
}

const connection = getDBConnection(req['keycloak_token']);

try {
const getSurveyTemplateS3KeySQLStatement = getSurveyTemplateS3KeySQL(
Number(req.params.surveyId),
Number(req.params.templateId)
);

if (!getSurveyTemplateS3KeySQLStatement) {
throw new HTTP400('Failed to build SQL get statement');
}

await connection.open();

const result = await connection.query(
getSurveyTemplateS3KeySQLStatement.text,
getSurveyTemplateS3KeySQLStatement.values
);

await connection.commit();

const s3Key = result && result.rows.length && result.rows[0].key;

const s3SignedUrl = await getS3SignedURL(s3Key);

if (!s3SignedUrl) {
return res.status(200).json(null);
}

return res.status(200).json(s3SignedUrl);
} catch (error) {
defaultLog.debug({ label: 'getSingleTemplateURL', message: 'error', error });
await connection.rollback();
throw error;
} finally {
connection.release();
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import chai, { expect } from 'chai';
import { describe } from 'mocha';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import * as templateObservations from './list';
import * as db from '../../../../../../database/db';
import * as template_observations_queries from '../../../../../../queries/occurrence/template-observation-queries';
import SQL from 'sql-template-strings';

chai.use(sinonChai);

describe('getTemplateObservations', () => {
const dbConnectionObj = {
systemUserId: () => {
return null;
},
open: async () => {
// do nothing
},
release: () => {
// do nothing
},
commit: async () => {
// do nothing
},
rollback: async () => {
// do nothing
},
query: async () => {
// do nothing
}
};

const sampleReq = {
keycloak_token: {},
body: {},
params: {
projectId: 1,
surveyId: 1
}
} as any;

let actualResult: any = null;

const sampleRes = {
status: () => {
return {
json: (result: any) => {
actualResult = result;
}
};
}
};

afterEach(() => {
sinon.restore();
});

it('should throw a 400 error when no surveyId is provided', async () => {
sinon.stub(db, 'getDBConnection').returns(dbConnectionObj);

try {
const result = templateObservations.getTemplateObservations();
await result(
{ ...sampleReq, params: { ...sampleReq.params, surveyId: null } },
(null as unknown) as any,
(null as unknown) as any
);
expect.fail();
} catch (actualError) {
expect(actualError.status).to.equal(400);
expect(actualError.message).to.equal('Missing required path param `surveyId`');
}
});

it('should throw a 400 error when no sql statement returned for getTemplateObservationsSQL', async () => {
sinon.stub(db, 'getDBConnection').returns({
...dbConnectionObj,
systemUserId: () => {
return 20;
}
});

sinon.stub(template_observations_queries, 'getTemplateObservationsSQL').returns(null);

try {
const result = templateObservations.getTemplateObservations();

await result(sampleReq, (null as unknown) as any, (null as unknown) as any);
expect.fail();
} catch (actualError) {
expect(actualError.status).to.equal(400);
expect(actualError.message).to.equal('Failed to build SQL get statement');
}
});

it('should return a list of survey template observations where the lastModified is the create_date', async () => {
const mockQuery = sinon.stub();

mockQuery.resolves({
rows: [
{ id: 13, key: 'projects/1/surveys/1/filename.txt', create_date: '2020-01-01', update_date: '', file_size: 50 }
]
});

sinon.stub(db, 'getDBConnection').returns({
...dbConnectionObj,
systemUserId: () => {
return 20;
},
query: mockQuery
});

sinon.stub(template_observations_queries, 'getTemplateObservationsSQL').returns(SQL`something`);

const result = templateObservations.getTemplateObservations();

await result(sampleReq, sampleRes as any, (null as unknown) as any);

expect(actualResult).to.be.eql({
templateObservationsList: [
{ fileName: 'projects/1/surveys/1/filename.txt', id: 13, lastModified: '2020-01-01', size: 50 }
]
});
});

it('should return a list of template observations where the lastModified is the update_date', async () => {
const mockQuery = sinon.stub();

mockQuery.resolves({
rows: [
{
id: 13,
key: 'projects/1/surveys/1/filename.txt',
create_date: '2020-01-01',
update_date: '2020-01-02',
file_size: 50
}
]
});

sinon.stub(db, 'getDBConnection').returns({
...dbConnectionObj,
systemUserId: () => {
return 20;
},
query: mockQuery
});

sinon.stub(template_observations_queries, 'getTemplateObservationsSQL').returns(SQL`something`);

const result = templateObservations.getTemplateObservations();

await result(sampleReq, sampleRes as any, (null as unknown) as any);

expect(actualResult).to.be.eql({
templateObservationsList: [
{ fileName: 'projects/1/surveys/1/filename.txt', id: 13, lastModified: '2020-01-02', size: 50 }
]
});
});

it('should return null if the survey has no template observations, on success', async () => {
const mockQuery = sinon.stub();

mockQuery.resolves({ rows: undefined });

sinon.stub(db, 'getDBConnection').returns({
...dbConnectionObj,
systemUserId: () => {
return 20;
},
query: mockQuery
});

sinon.stub(template_observations_queries, 'getTemplateObservationsSQL').returns(SQL`something`);

const result = templateObservations.getTemplateObservations();

await result(sampleReq, sampleRes as any, (null as unknown) as any);

expect(actualResult).to.be.null;
});
});
Loading