Skip to content

Commit

Permalink
feat: FORMS-1042 new template rendering route (bcgov#1323)
Browse files Browse the repository at this point in the history
* feat/FORMS-1042 new route for document generation

* added docs
  • Loading branch information
WalterMoar committed Apr 17, 2024
1 parent 94566a7 commit 0f51715
Show file tree
Hide file tree
Showing 8 changed files with 702 additions and 157 deletions.
116 changes: 91 additions & 25 deletions app/src/docs/v1.api-spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ tags:
This section of the API includes endpoints used to perform various
operations related to form drafts, for example create or publish a draft
from a specific version of a form.
- name: Document Template
- name: Document Templates
description: >
Documents can be generated using the Common Document Generation Service
([CDOGS](https://bcgov.github.io/common-service-showcase/services/cdogs.html)).
Expand All @@ -57,22 +57,20 @@ tags:
CHEFS app itself) until they stabilize.
The MVP workflow is that a form has a single active document template:
Currently only a single document template per form is supported by the
CHEFS application:
1. The `POST /forms/{formId}/documentTemplates` route is used to create
a document template for a form.
2. Subsequent calls to the `POST` route should be paired with calls to
the `DELETE` route to delete the previously active template.
3. When it comes time to generate the document, the
`GET /forms/{formId}/documentTemplates` route is used to get "all"
the document template metadata for a form (although there is only one
active document template). This route only returns metadata and not
the (possibly huge) template files themselves.
4. The `GET /forms/{formId}/documentTemplates/{documentTemplateId}`
route is used to get the specific document template's template file.
This "double GET" doesn't make a lot of sense when there is only one
document template for a form, but this will change soon when we allow the
user to choose which template they want to use for document generation.
Submission metadata is available for use in the document templates:
- `{d.chefs.submissionId}`: the unique identifier for the submission,
such as `3cb9acc7-cfd8-4491-b091-1277bc0ec303`
- `{d.chefs.confirmationId}`: The uppercased first eight characters of
the `submissionId`, such as `3CB9ACC7`
- `{d.chefs.formVersion}`: The numeric version of the form that was used
to create the submission, such as `1`
- name: Submission
description: >-
These API endpoints handle the input data provided by a user that
Expand Down Expand Up @@ -422,7 +420,7 @@ paths:
- BearerAuth: []
OpenID: []
tags:
- Document Template
- Document Templates
parameters:
- $ref: '#/components/parameters/formIdParam'
responses:
Expand Down Expand Up @@ -454,7 +452,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
post:
summary: Create a document template for a form
summary: Create a document template
description: >
Creates a document template for a form. This makes it easier for users
to generate documents, as the template is associated with the form and
Expand All @@ -465,7 +463,7 @@ paths:
- BearerAuth: []
OpenID: []
tags:
- Document Template
- Document Templates
parameters:
- $ref: '#/components/parameters/formIdParam'
requestBody:
Expand Down Expand Up @@ -502,7 +500,7 @@ paths:
$ref: '#/components/schemas/Error'
/forms/{formId}/documentTemplates/{documentTemplateId}:
get:
summary: Get a document template for a form
summary: Get a document template
description: >
Gets a document template for the given form. The response will include
the `template` field, which is the actual document template file.
Expand All @@ -512,7 +510,7 @@ paths:
- BearerAuth: []
OpenID: []
tags:
- Document Template
- Document Templates
parameters:
- $ref: '#/components/parameters/formIdParam'
- $ref: '#/components/parameters/documentTemplateIdParam'
Expand Down Expand Up @@ -554,7 +552,7 @@ paths:
- BearerAuth: []
OpenID: []
tags:
- Document Template
- Document Templates
parameters:
- $ref: '#/components/parameters/formIdParam'
- $ref: '#/components/parameters/documentTemplateIdParam'
Expand All @@ -580,7 +578,6 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'

/forms/{formId}/export:
get:
summary: Export submissions for a form
Expand Down Expand Up @@ -1724,9 +1721,78 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
/submissions/{formSubmissionId}/template/{documentTemplateId}/render:
get:
summary: Generate document from submission ID and template ID
description: >-
Uses form submission data (given the form submission ID) and a document
template (given the document template ID) and generates a document
containing the submission data.
#### Common Document Generation Service (CDOGS)
This endpoint is a passthrough to CDOGS. Instead of using *BasicAuth* to
call this endpoint, it is highly recommended that you [directly call
CDOGS](https://bcgov.github.io/common-service-showcase/services/cdogs.html).
Benefits of calling CDOGS directly from your application:
- avoid CHEFS API rate limiting restrictions
- avoid CHEFS API timeout restrictions (for large documents)
- better performance (direct call instead of passthrough)
- ability to use the CDOGS template cache
- direct support from the CDOGS team for your specific Client ID
operationId: templateIdSubmissionIdRender
security:
- BasicAuth: []
- BearerAuth: []
OpenID: []
tags:
- Document Templates
parameters:
- $ref: '#/components/parameters/formSubmissionIdParam'
- $ref: '#/components/parameters/documentTemplateIdParam'
responses:
'200':
description: Returns the document template with merged variables
content:
application/octet-stream:
schema:
type: string
format: binary
description: Raw binary-encoded response
headers:
Content-Disposition:
schema:
type: string
description: >-
Indicates if a browser should render this resource inline or
treat as an attachment for download
example: attachment; filename=file.pdf
Content-Type:
schema:
type: string
description: The MIME-type of the binary file payload
example: application/pdf
RateLimit:
$ref: '#/components/headers/RateLimit'
RateLimit-Policy:
$ref: '#/components/headers/RateLimit-Policy'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/UnprocessableEntity'
'429':
$ref: '#/components/responses/TooManyRequests'
default:
description: Unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/submissions/{formSubmissionId}/template/render:
post:
summary: Generate a document by providing a document template
summary: Generate document from submission ID and template object
description: >-
Merges data from a form submission into a document template.
Expand All @@ -1741,13 +1807,13 @@ paths:
- better performance (direct call instead of passthrough)
- ability to use the CDOGS template cache
- direct support from the CDOGS team for your specific Client ID
operationId: uploadTemplateAndRenderReport
operationId: templateObjectSubmissionIdRender
security:
- BasicAuth: []
- BearerAuth: []
OpenID: []
tags:
- Document Template
- Document Templates
parameters:
- $ref: '#/components/parameters/formSubmissionIdParam'
requestBody:
Expand Down Expand Up @@ -3464,7 +3530,7 @@ components:
description: >
The file that is the document template to be used in document
generation.
example: Hello {firstName}
example: Hello {d.firstName}
DocumentTemplateResponse:
allOf:
- $ref: '#/components/schemas/DocumentTemplateResponseBasic'
Expand All @@ -3475,7 +3541,7 @@ components:
description: >
The file that is the document template to be used in document
generation.
example: Hello {firstName}
example: Hello {d.firstName}
DocumentTemplateResponseBasic:
allOf:
- type: object
Expand Down
19 changes: 17 additions & 2 deletions app/src/forms/common/middleware/validateParameter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const Problem = require('api-problem');
const uuid = require('uuid');

const formService = require('../../form/service');
const submissionService = require('../../submission/service');

/**
* Throws a 400 problem if the parameter is not a valid UUID.
Expand All @@ -20,7 +21,8 @@ const _validateUuid = (parameter, parameterName) => {

/**
* Validates that the :documentTemplateId route parameter exists and is a UUID.
* This validator requires that the :formId route parameter also exists.
* This validator requires that either the :formId or :formSubmissionId route
* parameter also exists.
*
* @param {*} req the Express object representing the HTTP request
* @param {*} _res the Express object representing the HTTP response - unused
Expand All @@ -31,8 +33,21 @@ const validateDocumentTemplateId = async (req, _res, next, documentTemplateId) =
try {
_validateUuid(documentTemplateId, 'documentTemplateId');

let formId = req.params.formId;
if (!formId) {
const formSubmissionId = req.params.formSubmissionId;
if (!formSubmissionId) {
throw new Problem(404, {
detail: 'documentTemplateId does not exist on this form',
});
}

const submission = await submissionService.read(formSubmissionId);
formId = submission.form.id;
}

const documentTemplate = await formService.documentTemplateRead(documentTemplateId);
if (!documentTemplate || documentTemplate.formId !== req.params.formId) {
if (!documentTemplate || documentTemplate.formId !== formId) {
throw new Problem(404, {
detail: 'documentTemplateId does not exist on this form',
});
Expand Down
76 changes: 75 additions & 1 deletion app/src/forms/submission/controller.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const { Statuses } = require('../common/constants');
const cdogsService = require('../../components/cdogsService');

const { Statuses } = require('../common/constants');
const emailService = require('../email/emailService');
const formService = require('../form/service');

const service = require('./service');

module.exports = {
Expand Down Expand Up @@ -119,6 +122,66 @@ module.exports = {
next(error);
}
},

/**
* Takes a document template ID and a form submission ID and renders the
* template into a document.
*
* @param {Object} req the Express object representing the HTTP request.
* @param {Object} res the Express object representing the HTTP response.
* @param {Object} next the Express chaining function.
*/
templateRender: async (req, res, next) => {
try {
const submission = await service.read(req.params.formSubmissionId);
const template = await formService.documentTemplateRead(req.params.documentTemplateId);
const fileName = template.filename.substring(0, template.filename.lastIndexOf('.'));
const fileExtension = template.filename.substring(template.filename.lastIndexOf('.') + 1);

const templateBody = {
data: {
...submission.submission.submission.data,
chefs: {
confirmationId: submission.submission.confirmationId,
formVersion: submission.version.version,
submissionId: submission.submission.id,
},
},
options: {
convertTo: 'pdf',
overwrite: true,
reportName: fileName,
},
template: {
content: btoa(template.template),
encodingType: 'base64',
fileType: fileExtension,
},
};

const { data, headers, status } = await cdogsService.templateUploadAndRender(templateBody);
const contentDisposition = headers['content-disposition'];

res
.status(status)
.set({
'Content-Disposition': contentDisposition ? contentDisposition : 'attachment',
'Content-Type': headers['content-type'],
})
.send(data);
} catch (error) {
next(error);
}
},

/**
* Takes a document template file and a form submission ID and renders the
* template into a document.
*
* @param {Object} req the Express object representing the HTTP request.
* @param {Object} res the Express object representing the HTTP response.
* @param {Object} next the Express chaining function.
*/
templateUploadAndRender: async (req, res, next) => {
try {
const submission = await service.read(req.params.formSubmissionId);
Expand All @@ -128,6 +191,7 @@ module.exports = {
...submission.submission.submission.data,
chefs: {
confirmationId: submission.submission.confirmationId,
formVersion: submission.version.version,
submissionId: submission.submission.id,
},
},
Expand All @@ -146,6 +210,15 @@ module.exports = {
next(error);
}
},

/**
* Takes a document template file and a form submission object and renders the
* template into a document.
*
* @param {Object} req the Express object representing the HTTP request.
* @param {Object} res the Express object representing the HTTP response.
* @param {Object} next the Express chaining function.
*/
draftTemplateUploadAndRender: async (req, res, next) => {
try {
const templateBody = { ...req.body.template, data: req.body.submission.data };
Expand All @@ -163,6 +236,7 @@ module.exports = {
next(error);
}
},

listEdits: async (req, res, next) => {
try {
const response = await service.listEdits(req.params.formSubmissionId);
Expand Down
Loading

0 comments on commit 0f51715

Please sign in to comment.