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

Implement functionality to edit draft projects #238

Merged
merged 14 commits into from
Apr 16, 2021
22 changes: 22 additions & 0 deletions api/src/openapi/schemas/draft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,25 @@ export const draftResponseObject = {
}
}
};

/**
* Response object for getting a draft
*/
export const draftGetResponseObject = {
title: 'Draft Get Response Object',
type: 'object',
required: ['id', 'name', 'data'],
properties: {
id: {
type: 'number'
},
name: {
type: 'string',
description: 'The name of the draft'
},
data: {
type: 'string',
description: 'The data associated with this draft'
}
}
};
85 changes: 85 additions & 0 deletions api/src/paths/draft/{draftId}/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use strict';

import { RequestHandler } from 'express';
import { Operation } from 'express-openapi';
import { getDBConnection } from '../../../database/db';
import { HTTP400 } from '../../../errors/CustomError';
import { getLogger } from '../../../utils/logger';
import { WRITE_ROLES } from '../../../constants/roles';
import { deleteDraftSQL } from '../../../queries/draft-queries';

const defaultLog = getLogger('/api/draft/{draftId}/delete');

export const DELETE: Operation = [deleteDraft()];

DELETE.apiDoc = {
description: 'Delete a draft record.',
tags: ['attachment'],
security: [
{
Bearer: WRITE_ROLES
}
],
parameters: [
{
in: 'path',
name: 'draftId',
schema: {
type: 'number'
},
required: true
}
],
responses: {
200: {
description: 'Row count of successfully deleted draft record',
content: {
'text/plain': {
schema: {
type: 'number'
}
}
}
},
401: {
$ref: '#/components/responses/401'
},
default: {
$ref: '#/components/responses/default'
}
}
};

function deleteDraft(): RequestHandler {
return async (req, res) => {
defaultLog.debug({ label: 'Delete draft', message: 'params', req_params: req.params });

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

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

try {
await connection.open();

const deleteDraftSQLStatement = deleteDraftSQL(Number(req.params.draftId));

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

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

await connection.commit();

return res.status(200).json(result && result.rowCount);
} catch (error) {
defaultLog.debug({ label: 'deleteDraft', message: 'error', error });
await connection.rollback();
throw error;
} finally {
connection.release();
}
};
}
96 changes: 96 additions & 0 deletions api/src/paths/draft/{draftId}/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { RequestHandler } from 'express';
import { Operation } from 'express-openapi';
import { READ_ROLES } from '../../../constants/roles';
import { getDBConnection } from '../../../database/db';
import { HTTP400 } from '../../../errors/CustomError';
import { draftGetResponseObject } from '../../../openapi/schemas/draft';
import { getDraftSQL } from '../../../queries/draft-queries';
import { getLogger } from '../../../utils/logger';
import { logRequest } from '../../../utils/path-utils';

const defaultLog = getLogger('paths/draft/{draftId}');

export const GET: Operation = [logRequest('paths/draft/{draftId}', 'GET'), getSingleDraft()];

GET.apiDoc = {
description: 'Get a draft.',
tags: ['draft'],
security: [
{
Bearer: READ_ROLES
}
],
parameters: [
{
in: 'path',
name: 'draftId',
schema: {
type: 'number'
},
required: true
}
],
responses: {
200: {
description: 'Draft with matching draftId.',
content: {
'application/json': {
schema: {
...(draftGetResponseObject as 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'
}
}
};

/**
* Get a draft by its id.
*
* @returns {RequestHandler}
*/
function getSingleDraft(): RequestHandler {
return async (req, res) => {
const connection = getDBConnection(req['keycloak_token']);

try {
const getDraftSQLStatement = getDraftSQL(Number(req.params.draftId));

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

await connection.open();

const draftResponse = await connection.query(getDraftSQLStatement.text, getDraftSQLStatement.values);

await connection.commit();

const draftResult = (draftResponse && draftResponse.rows && draftResponse.rows[0]) || null;

defaultLog.debug('draftResult:', draftResult);

return res.status(200).json(draftResult);
} catch (error) {
defaultLog.debug({ label: 'getSingleDraft', message: 'error', error });
throw error;
} finally {
connection.release();
}
};
}
2 changes: 1 addition & 1 deletion api/src/paths/project/{projectId}/attachments/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { GetAttachmentsData } from '../../../../models/project-attachments';
import { getProjectAttachmentsSQL } from '../../../../queries/project/project-attachments-queries';
import { getLogger } from '../../../../utils/logger';

const defaultLog = getLogger('/api/projects/{projectId}/artifacts/attachments/view');
const defaultLog = getLogger('/api/project/{projectId}/attachments/list');

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

Expand Down
8 changes: 4 additions & 4 deletions api/src/paths/project/{projectId}/attachments/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import { uploadFileToS3 } from '../../../../utils/file-utils';
import { getLogger } from '../../../../utils/logger';
import { upsertProjectAttachment } from '../../../project';

const defaultLog = getLogger('/api/projects/{projectId}/artifacts/upload');
const defaultLog = getLogger('/api/project/{projectId}/attachments/upload');

export const POST: Operation = [uploadMedia()];
POST.apiDoc = {
description: 'Upload project-specific artifacts.',
description: 'Upload project-specific attachments.',
tags: ['artifacts'],
security: [
{
Expand All @@ -37,7 +37,7 @@ POST.apiDoc = {
properties: {
media: {
type: 'array',
description: 'An array of artifacts to upload',
description: 'An array of attachments to upload',
items: {
type: 'string',
format: 'binary'
Expand All @@ -50,7 +50,7 @@ POST.apiDoc = {
},
responses: {
200: {
description: 'Artifacts upload response.',
description: 'Attachments upload response.',
content: {
'application/json': {
schema: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import { HTTP400 } from '../../../../../errors/CustomError';
import { deleteProjectAttachmentSQL } from '../../../../../queries/project/project-attachments-queries';
import { deleteFileFromS3 } from '../../../../../utils/file-utils';
import { getLogger } from '../../../../../utils/logger';
import { getAttachmentApiDocObject } from '../../../../../utils/shared-api-docs';
import { attachmentApiDocObject } from '../../../../../utils/shared-api-docs';

const defaultLog = getLogger('/api/projects/{projectId}/artifacts/attachments/{attachmentId}/delete');
const defaultLog = getLogger('/api/project/{projectId}/attachments/{attachmentId}/delete');

export const DELETE: Operation = [deleteAttachment()];

DELETE.apiDoc = getAttachmentApiDocObject(
DELETE.apiDoc = attachmentApiDocObject(
'Delete an attachment of a project.',
'Row count of successfully deleted attachment record'
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import { getLogger } from '../../../../../utils/logger';
import { getDBConnection } from '../../../../../database/db';
import { getProjectAttachmentS3KeySQL } from '../../../../../queries/project/project-attachments-queries';
import { getS3SignedURL } from '../../../../../utils/file-utils';
import { getAttachmentApiDocObject } from '../../../../../utils/shared-api-docs';
import { attachmentApiDocObject } from '../../../../../utils/shared-api-docs';

const defaultLog = getLogger('/api/projects/{projectId}/artifacts/attachments/{attachmentId}/view');
const defaultLog = getLogger('/api/project/{projectId}/attachments/{attachmentId}/getSignedUrl');

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

GET.apiDoc = getAttachmentApiDocObject(
GET.apiDoc = attachmentApiDocObject(
'Retrieves the signed url of an attachment in a project by its file name.',
'GET response containing the signed url of an attachment.'
);
Expand Down
26 changes: 25 additions & 1 deletion api/src/queries/draft-queries.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from 'chai';
import { describe } from 'mocha';
import { getDraftsSQL, postDraftSQL, putDraftSQL } from './draft-queries';
import { deleteDraftSQL, getDraftSQL, getDraftsSQL, postDraftSQL, putDraftSQL } from './draft-queries';

describe('postDraftSQL', () => {
it('Null systemUserId', () => {
Expand Down Expand Up @@ -71,3 +71,27 @@ describe('getDraftsSQL', () => {
expect(response).to.not.be.null;
});
});

describe('getDraftSQL', () => {
it('Null draftId', () => {
const response = getDraftSQL((null as unknown) as number);
expect(response).to.be.null;
});

it('Valid parameters', () => {
const response = getDraftSQL(1);
expect(response).to.not.be.null;
});
});

describe('deleteDraftSQL', () => {
it('Null draftId', () => {
const response = deleteDraftSQL((null as unknown) as number);
expect(response).to.be.null;
});

it('Valid parameters', () => {
const response = deleteDraftSQL(1);
expect(response).to.not.be.null;
});
});
62 changes: 62 additions & 0 deletions api/src/queries/draft-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,65 @@ export const getDraftsSQL = (systemUserId: number): SQLStatement | null => {

return sqlStatement;
};

/**
* SQL query to get a single draft from the webform_draft table.
*
* @param {number} draftId
* @return {SQLStatement} {(SQLStatement | null)}
*/
export const getDraftSQL = (draftId: number): SQLStatement | null => {
defaultLog.debug({ label: 'getDraftSQL', message: 'params', draftId });

if (!draftId) {
return null;
}

const sqlStatement: SQLStatement = SQL`
SELECT
id,
name,
data
FROM
webform_draft
WHERE
id = ${draftId};
`;

defaultLog.debug({
label: 'getDraftSQL',
message: 'sql',
'sqlStatement.text': sqlStatement.text,
'sqlStatement.values': sqlStatement.values
});

return sqlStatement;
};

/**
* SQL query to delete a single draft from the webform_draft table.
*
* @param {number} draftId
* @return {SQLStatement} {(SQLStatement) | null}
*/
export const deleteDraftSQL = (draftId: number): SQLStatement | null => {
defaultLog.debug({ label: 'deleteDraftSQL', message: 'params', draftId });

if (!draftId) {
return null;
}

const sqlStatement: SQLStatement = SQL`
DELETE from webform_draft
WHERE id = ${draftId};
`;

defaultLog.debug({
label: 'deleteDraftSQL',
message: 'sql',
'sqlStatement.text': sqlStatement.text,
'sqlStatement.values': sqlStatement.values
});

return sqlStatement;
};
Loading