Skip to content

Commit

Permalink
SDIT-2353: ✨ Add in gotenberg and start on download (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
petergphillips authored Dec 11, 2024
1 parent 446dc71 commit 016b7b8
Show file tree
Hide file tree
Showing 21 changed files with 417 additions and 12 deletions.
20 changes: 20 additions & 0 deletions docker-compose-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,25 @@ services:
ports:
- "9091:8080"

gotenberg:
image: gotenberg/gotenberg:8.14.1
networks:
- hmpps_int
container_name: gotenberg
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "3001:3000"
command:
- "gotenberg"
- "--chromium-ignore-certificate-errors"
- "--pdfengines-merge-engines=pdftk"
- "--libreoffice-disable-routes"
- "--webhook-disable"
- "--prometheus-disable-collect"
restart: always
healthcheck:
test: [ 'CMD', 'curl', '-f', 'http://localhost:3001/health' ]

networks:
hmpps_int:
20 changes: 20 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,25 @@ services:
- INGRESS_URL=http://localhost:3000
- NO_HTTPS=true

gotenberg:
image: gotenberg/gotenberg:8.14.1
networks:
- hmpps
container_name: gotenberg
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "3001:3000"
command:
- "gotenberg"
- "--chromium-ignore-certificate-errors"
- "--pdfengines-merge-engines=pdftk"
- "--libreoffice-disable-routes"
- "--webhook-disable"
- "--prometheus-disable-collect"
restart: always
healthcheck:
test: [ 'CMD', 'curl', '-f', 'http://localhost:3001/health' ]

networks:
hmpps:
1 change: 1 addition & 0 deletions feature.env
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ HISTORICAL_PRISONER_API_URL=http://localhost:9091/historical-prisoner-api
TOKEN_VERIFICATION_API_URL=http://localhost:9091/verification
TOKEN_VERIFICATION_ENABLED=true
COMPONENT_API_URL=http://localhost:9091/frontend-components
GOTENBERG_API_URL=http://localhost:3001
REDIS_ENABLED=false
NODE_ENV=development
AUTH_CODE_CLIENT_ID=clientid
Expand Down
4 changes: 4 additions & 0 deletions helm_deploy/hmpps-historical-prisoner/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ dependencies:
- name: generic-prometheus-alerts
version: "1.11"
repository: https://ministryofjustice.github.io/hmpps-helm-charts
- name: generic-service
alias: gotenberg
version: "3.8"
repository: https://ministryofjustice.github.io/hmpps-helm-charts
46 changes: 46 additions & 0 deletions helm_deploy/hmpps-historical-prisoner/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,49 @@ generic-service:

generic-prometheus-alerts:
targetApplication: hmpps-historical-prisoner

gotenberg:
nameOverride: gotenberg
replicaCount: 4

image:
repository: gotenberg/gotenberg
tag: 8.14.1
port: 3000

containerCommand: [ "gotenberg" ]
containerArgs:
[
"--chromium-ignore-certificate-errors",
"--pdfengines-merge-engines=pdftk",
"--libreoffice-disable-routes",
"--webhook-disable",
"--prometheus-disable-collect"
]

ingress:
enabled: false

livenessProbe:
httpGet:
path: /health
periodSeconds: 30
initialDelaySeconds: 60
timeoutSeconds: 20
failureThreshold: 10

readinessProbe:
httpGet:
path: /health
periodSeconds: 20
initialDelaySeconds: 60
timeoutSeconds: 30
failureThreshold: 15

podSecurityContext:
fsGroup: 1001

securityContext:
runAsUser: 1001
privileged: false
runAsNonRoot: true
3 changes: 3 additions & 0 deletions helm_deploy/values-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ generic-service:
generic-prometheus-alerts:
businessHoursOnly: true
alertSeverity: syscon-alerts-non-prod

gotenberg:
replicaCount: 2
3 changes: 3 additions & 0 deletions helm_deploy/values-preprod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ generic-service:
generic-prometheus-alerts:
businessHoursOnly: true
alertSeverity: syscon-alerts-non-prod

gotenberg:
replicaCount: 2
17 changes: 8 additions & 9 deletions integration_tests/e2e/print.cy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Page from '../pages/page'
import Disclaimer from '../pages/disclaimer'
import PrintPage from '../pages/print'
import DetailPage from '../pages/detail'

context('Print', () => {
beforeEach(() => {
Expand Down Expand Up @@ -30,10 +29,10 @@ context('Print', () => {
const printPage = Page.verifyOnPageWithTitleParam(PrintPage, 'Firsta SURNAMEA')
printPage.optionCheckbox('Subject').click()
printPage.optionCheckbox('Court').click()
printPage.saveButton().click()

// TODO: change to pdf
Page.verifyOnPageWithTitleParam(DetailPage, 'Firsta Middlea SURNAMEA')
// TODO: add back in and change to pdf
// printPage.saveButton().click()
//
// Page.verifyOnPageWithTitleParam(DetailPage, 'Firsta Middlea SURNAMEA')
})

it('Will allow user to select all sections', () => {
Expand All @@ -42,9 +41,9 @@ context('Print', () => {
cy.visit('/print/A1234BC')
const printPage = Page.verifyOnPageWithTitleParam(PrintPage, 'Firsta SURNAMEA')
printPage.optionCheckbox('All, I would like all details').click()
printPage.saveButton().click()

// TODO: change to pdf
Page.verifyOnPageWithTitleParam(DetailPage, 'Firsta Middlea SURNAMEA')
// TODO: add back in and change to pdf
// printPage.saveButton().click()
//
// Page.verifyOnPageWithTitleParam(DetailPage, 'Firsta Middlea SURNAMEA')
})
})
13 changes: 13 additions & 0 deletions server/@types/express/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { HmppsUser } from '../../interfaces/hmppsUser'
import { PdfMargins } from '../../data/gotenbergClient'

export declare module 'express-session' {
export interface PrisonerSearchForm {
Expand Down Expand Up @@ -51,6 +52,18 @@ export declare global {
interface Locals {
user: HmppsUser
}

interface Response {
renderPdf(
pageView: string,
pageData: PdfPageData,
headerView: string,
headerData: PdfHeaderData,
footerView: string,
footerData: PdfFooterData,
options: { filename: string; pdfMargins: PdfMargins },
): void
}
}
}
type SearchParams = Partial<{
Expand Down
3 changes: 3 additions & 0 deletions server/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import express from 'express'

import createError from 'http-errors'
import dpsComponents from '@ministryofjustice/hmpps-connect-dps-components'
import GotenbergClient from './data/gotenbergClient'

import nunjucksSetup from './utils/nunjucksSetup'
import errorHandler from './errorHandler'
Expand All @@ -21,6 +22,7 @@ import setUpWebSession from './middleware/setUpWebSession'
import routes from './routes'
import config from './config'
import type { Services } from './services'
import { pdfRenderer } from './utils/pdfRenderer'

export default function createApp(services: Services): express.Application {
const app = express()
Expand All @@ -37,6 +39,7 @@ export default function createApp(services: Services): express.Application {
app.use(setUpStaticResources())
nunjucksSetup(app)
app.use(setUpAuthentication())
app.use(pdfRenderer(new GotenbergClient(config.apis.gotenberg.apiUrl)))
app.use(authorisationMiddleware(['ROLE_HPA_USER']))
app.use(setUpCsrf())

Expand Down
10 changes: 10 additions & 0 deletions server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ export default {
},
agent: new AgentConfig(Number(get('HISTORICAL_PRISONER_API_TIMEOUT_RESPONSE', 20000))),
},
gotenberg: {
apiUrl: get('GOTENBERG_API_URL', 'http://localhost:3001', requiredInProduction),
pdfMargins: {
marginTop: '1.0',
marginBottom: '0.8',
marginLeft: '0.0',
marginRight: '0.0',
scale: '0.93',
},
},
tokenVerification: {
url: get('TOKEN_VERIFICATION_API_URL', 'http://localhost:8100', requiredInProduction),
timeout: {
Expand Down
43 changes: 43 additions & 0 deletions server/data/gotenbergClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import superagent from 'superagent'

export type PdfMargins = {
marginTop?: string
marginBottom?: string
marginLeft?: string
marginRight?: string
}

export default class GotenbergClient {
private gotenbergHost: string

constructor(gotenbergHost: string) {
this.gotenbergHost = gotenbergHost
}

async renderPdfFromHtml(
html: string,
headerHtml: string,
footerHtml: string,
options: PdfMargins = {},
): Promise<Buffer> {
const { marginBottom, marginLeft, marginRight, marginTop } = options
const request = superagent
.post(`${this.gotenbergHost}/forms/chromium/convert/html`)
.set('Content-Type', 'multi-part/form-data')
.buffer(true)
.attach('files', Buffer.from(html), 'index.html')
.attach('files', Buffer.from(headerHtml), 'header.html')
.attach('files', Buffer.from(footerHtml), 'footer.html')
.responseType('blob')

// Gotenberg defaults to using A4 format. Page size and margins specified in inches
if (marginTop) request.field('marginTop', marginTop)
if (marginBottom) request.field('marginBottom', marginBottom)
if (marginLeft) request.field('marginLeft', marginLeft)
if (marginRight) request.field('marginRight', marginRight)

// Execute the POST to the Gotenberg container
const response = await request
return response.body
}
}
3 changes: 2 additions & 1 deletion server/routes/detail/abstractDetailController.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Request } from 'express'
import HistoricalPrisonerService from '../../services/historicalPrisonerService'
import { PrisonerDetailDto } from '../../@types/historical-prisoner/historicalPrisonerApiTypes'

export default abstract class AbstractDetailController {
protected constructor(protected readonly historicalPrisonerService: HistoricalPrisonerService) {}

protected async getPrisonerDetail(req: Request) {
protected async getPrisonerDetail(req: Request): Promise<PrisonerDetailDto> {
const { prisonNo } = req.params
if (prisonNo !== req.session.prisonerDetail?.prisonNumber) {
// update session if we don't have the correct or any prison details
Expand Down
26 changes: 25 additions & 1 deletion server/routes/print/printController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Request, Response } from 'express'
import PrintController from './printController'
import HistoricalPrisonerService from '../../services/historicalPrisonerService'
import auditServiceMock from '../../testutils/auditServiceMock'
import config from '../../config'

jest.mock('../../services/historicalPrisonerService')
const historicalPrisonerService = new HistoricalPrisonerService() as jest.Mocked<HistoricalPrisonerService>
Expand All @@ -26,6 +27,7 @@ describe('Print controller', () => {
render: jest.fn(),
redirect: jest.fn(),
status: jest.fn(),
renderPdf: jest.fn(),
} as unknown as Response
})

Expand Down Expand Up @@ -117,11 +119,33 @@ describe('Print controller', () => {

await controller.postPrintForm(req, res)

expect(res.redirect).toHaveBeenCalledWith('/detail/AB12345')
expect(res.redirect).toHaveBeenCalledWith('/print/AB12345/pdf')
})
})
})

describe('renderPdf', () => {
it('should render PDF with correct parameters', async () => {
historicalPrisonerService.getPrisonerDetail.mockResolvedValue(detail)
req.params = { prisonNo: 'AB12345' }

await controller.renderPdf(req, res)

expect(res.renderPdf).toHaveBeenCalledWith(
'pages/pdf',
{ ...detail },
'pages/pdfHeader',
{ ...detail },
'pages/pdfFooter',
{},
{
filename: 'print-AB12345.pdf',
pdfMargins: config.apis.gotenberg.pdfMargins,
},
)
})
})

function expectRenderPrintContainingDetail() {
expect(res.render).toHaveBeenCalledWith('pages/print', expect.objectContaining({ summary: detail.summary }))
}
Expand Down
21 changes: 20 additions & 1 deletion server/routes/print/printController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import HistoricalPrisonerService from '../../services/historicalPrisonerService'
import AuditService from '../../services/auditService'
import HmppsError from '../../interfaces/HmppsError'
import AbstractDetailController from '../detail/abstractDetailController'
import config from '../../config'

type ItemType = { divider?: string; value?: string; text?: string; behaviour?: string }

Expand Down Expand Up @@ -48,7 +49,25 @@ export default class PrintController extends AbstractDetailController {
return this.renderView(req, res, { errors })
}
const { prisonNo } = req.params
return res.redirect(`/detail/${prisonNo}`)
// TODO: pass the selected sections to the pdf render service
return res.redirect(`/print/${prisonNo}/pdf`)
}

async renderPdf(req: Request, res: Response): Promise<void> {
const prisonerDetail = await this.getPrisonerDetail(req)
const { pdfMargins } = config.apis.gotenberg
res.renderPdf(
`pages/pdf`,
{ ...prisonerDetail },
`pages/pdfHeader`,
{ ...prisonerDetail },
`pages/pdfFooter`,
{},
{
filename: `print-${prisonerDetail.prisonNumber}.pdf`,
pdfMargins,
},
)
}

async renderView(req: Request, res: Response, pageData?: PageData): Promise<void> {
Expand Down
1 change: 1 addition & 0 deletions server/routes/print/printRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default function routes(

get('/print/:prisonNo', async (req, res, next) => printController.getPrintForm(req, res))
post('/print/:prisonNo', async (req, res, next) => printController.postPrintForm(req, res))
get('/print/:prisonNo/pdf', async (req, res, next) => printController.renderPdf(req, res))

return router
}
Loading

0 comments on commit 016b7b8

Please sign in to comment.