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

Feature/merge files spinner #1477

Open
wants to merge 46 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
bfc9187
Endringer for sammenslåing av førsteside med søknad
nils-arne Feb 17, 2025
c18a36b
Endringer for sammenslåing av førsteside med søknad
nils-arne Feb 17, 2025
9c96e17
Merge remote-tracking branch 'origin/tech/node-22' into feature/merge…
nils-arne Feb 17, 2025
c9f971d
Endringer for sammenslåing av førsteside med søknad
nils-arne Feb 17, 2025
6b03d7b
Endringer for sammenslåing av førsteside med søknad
nils-arne Feb 17, 2025
f479706
Endringer for sammenslåing av førsteside med søknad
nils-arne Feb 19, 2025
40c5caa
Endringer for sammenslåing av førsteside med søknad
nils-arne Feb 19, 2025
54c8028
Endringer for sammenslåing av førsteside med søknad
nils-arne Feb 19, 2025
ea3970a
Endringer for sammenslåing av førsteside med søknad. Lagt til logging…
nils-arne Feb 19, 2025
213bc4a
Endringer for sammenslåing av førsteside med søknad. Nais konfigurasjon
nils-arne Feb 20, 2025
5ed31b3
Endringer for forberedelse til PR
nils-arne Feb 24, 2025
cab39ca
Merge remote-tracking branch 'origin/master' into feature/merge-files
nils-arne Feb 24, 2025
f26b881
Merging fra master og fikset pakke versjoner
nils-arne Feb 25, 2025
0b922c2
Oppdatering etter PR. Midlertidig beholdt ubrukt kode for generering …
nils-arne Mar 5, 2025
f25f18c
Oppdatering etter PR. Fjernet ubrukt kode for å generere PDF av søkna…
nils-arne Mar 5, 2025
c549e45
Merge remote-tracking branch 'origin/master' into feature/merge-files
nils-arne Mar 5, 2025
555f3e5
Merge branch 'master' into feature/merge-files
lotorvik Mar 5, 2025
720a89e
Lagt til håndtering av språk ved kall til Gotenberg
nils-arne Mar 12, 2025
5675c66
Merge remote-tracking branch 'origin/feature/merge-files' into featur…
nils-arne Mar 12, 2025
c57cb02
Change download pdf to regular fetch request with spinner
lotorvik Mar 12, 2025
2e2abbb
Merge branch 'feature/merge-files' into feature/merge-files-spinner
lotorvik Mar 13, 2025
1218248
Remove /first-page endpoint since ettersending could not use it
lotorvik Mar 13, 2025
31b04fb
Remove /first-page endpoint since ettersending could not use it
lotorvik Mar 14, 2025
f7c36f6
Remove message on next click and avoid multiple clicks
lotorvik Mar 14, 2025
9f4fcf5
Avoid multiple clicks on button
lotorvik Mar 14, 2025
b0e96ea
Remove formPath not needed.
lotorvik Mar 14, 2025
95f74ec
Rename front page to cover page
lotorvik Mar 17, 2025
8a31ebd
Update texts
lotorvik Mar 19, 2025
363d5c3
Sanitize filename
lotorvik Mar 19, 2025
8c199bc
Language support on filename
lotorvik Mar 19, 2025
d1d8385
Merge branch 'master' into feature/merge-files-spinner
lotorvik Mar 19, 2025
6fe54fa
Support only download application
lotorvik Mar 19, 2025
57d9348
Fikset feil på tester
nils-arne Mar 20, 2025
fc466b0
Fikset feil på tester. Lagt til test. Fikset route for merge av filer…
nils-arne Mar 21, 2025
b8cc789
Change endpoint for gotenberg test
lotorvik Mar 24, 2025
6d44354
Remove filename sanitize, use form path instead like we used to do in…
lotorvik Mar 24, 2025
75dc4d8
Verify we call the correct endpoint on ingen innsending
lotorvik Mar 24, 2025
3cdbf77
Fix test
lotorvik Mar 24, 2025
0647f47
Fix translations
lotorvik Mar 25, 2025
9a6cf0d
Fix translation for cover page
lotorvik Mar 25, 2025
0fe31a9
Add debug log
lotorvik Mar 26, 2025
7fc485b
Fix language code for generation pdf
lotorvik Mar 26, 2025
4f97af2
Test nginx.ingress.kubernetes.io/proxy-send-timeout change
lotorvik Mar 26, 2025
898de49
Test nginx.ingress.kubernetes.io/proxy-connect-timeout change
lotorvik Mar 26, 2025
e196173
Test nginx.ingress.kubernetes.io change
lotorvik Mar 26, 2025
ac297aa
Add english gotenberg as outbound.
lotorvik Mar 26, 2025
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
Prev Previous commit
Next Next commit
Oppdatering etter PR. Midlertidig beholdt ubrukt kode for generering …
…av Html og konvertering til PDF v.hj.a Gotenberg
nils-arne committed Mar 5, 2025
commit 0b922c2b74b4dad22dce5641f35e699f8edc9208
2 changes: 1 addition & 1 deletion .nais/fyllut/dev-delingslenke.yaml
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ foersteside-url: https://www.nav.no/soknader/api/forsteside
dekorator-url: https://www.nav.no/dekoratoren?simple=true
skjemabygging-proxy-client-id: 95170319-b4d7-4190-8271-118ed19bafbf
skjemabygging-proxy-url: https://skjemabygging-proxy.dev-fss-pub.nais.io
gotenberg-url: https://convert-to-pdf.intern.dev.nav.no
gotenberg-url: http://upload-convert-to-pdf.fyllut-sendinn
send-inn-token-x-client-id: dev-gcp:team-soknad:innsending-api
send-inn-host: http://innsending-api.team-soknad
forms-api-url: https://forms-api.nav.no
2 changes: 1 addition & 1 deletion .nais/fyllut/dev.yaml
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ foersteside-url: https://www.nav.no/soknader/api/forsteside
dekorator-url: https://www.nav.no/dekoratoren?simple=true
skjemabygging-proxy-client-id: 95170319-b4d7-4190-8271-118ed19bafbf
skjemabygging-proxy-url: https://skjemabygging-proxy.dev-fss-pub.nais.io
gotenberg-url: https://convert-to-pdf.intern.dev.nav.no
gotenberg-url: http://upload-convert-to-pdf.fyllut-sendinn
send-inn-token-x-client-id: dev-gcp:team-soknad:innsending-api
send-inn-host: http://innsending-api.team-soknad
kodeverk:
2 changes: 1 addition & 1 deletion .nais/fyllut/preprod-alt.yaml
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ foersteside-url: https://www.nav.no/soknader/api/forsteside
dekorator-url: https://www.nav.no/dekoratoren?simple=true
skjemabygging-proxy-client-id: 95170319-b4d7-4190-8271-118ed19bafbf
skjemabygging-proxy-url: https://skjemabygging-proxy.dev-fss-pub.nais.io
gotenberg-url: https://convert-to-pdf.intern.dev.nav.no
gotenberg-url: http://upload-convert-to-pdf.fyllut-sendinn
send-inn-token-x-client-id: dev-gcp:team-soknad:innsending-api
send-inn-host: http://innsending-api.team-soknad
kodeverk:
2 changes: 1 addition & 1 deletion .nais/fyllut/preprod.yaml
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ foersteside-url: https://www.nav.no/soknader/api/forsteside
dekorator-url: https://www.nav.no/dekoratoren?simple=true
skjemabygging-proxy-client-id: 95170319-b4d7-4190-8271-118ed19bafbf
skjemabygging-proxy-url: https://skjemabygging-proxy.dev-fss-pub.nais.io
gotenberg-url: https://convert-to-pdf.intern.dev.nav.no
gotenberg-url: http://upload-convert-to-pdf.fyllut-sendinn
send-inn-token-x-client-id: dev-gcp:team-soknad:innsending-api
send-inn-host: http://innsending-api.team-soknad
kodeverk:
2 changes: 1 addition & 1 deletion packages/fyllut-backend/package.json
Original file line number Diff line number Diff line change
@@ -52,7 +52,7 @@
"@types/node-fetch": "^2.6.12",
"@types/node-jose": "^1.1.13",
"@types/supertest": "^6.0.2",
"nock": "^14",
"nock": "^14.0.1",
"supertest": "^7.0.0",
"vite-plugin-node": "^4.0.0",
"vite-plugin-static-copy": "^2.2.0"
13 changes: 3 additions & 10 deletions packages/fyllut-backend/src/routers/api/exstream.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { readFileSync } from 'fs';
import nock from 'nock';
import path from 'path';
import { mockRequest, mockResponse } from '../../test/testHelpers';
import { base64Encode } from '../../utils/base64';
import { base64Decode } from '../../utils/base64';
import exstream from './exstream';

const formTitle = 'testskjema';
@@ -14,22 +12,17 @@ const defaultBody = {
language: 'nb-NO',
};

const filePath = path.join(process.cwd(), '/src/routers/api/test.pdf');

describe('exstream', () => {
it('decodes and sends the pdf on success', async () => {
const testPdf = readFileSync(filePath, { encoding: 'utf-8', flag: 'r' });
const encodedPdf = base64Encode(testPdf);

const skjemabyggingproxyScope = nock(process.env.SKJEMABYGGING_PROXY_URL as string)
.post('/exstream')
.reply(200, { data: { result: [{ content: { data: encodedPdf } }] } }, { 'content-type': 'application/json' });
.reply(200, { data: { result: [{ content: { data: 'base64EncodedPDFstring' } }] } });
const req = mockRequest({ headers: { AzureAccessToken: 'azure-access-token' }, body: defaultBody });
const res = mockResponse();
const next = vi.fn();
await exstream.post(req, res, next);
expect(next).not.toHaveBeenCalled();
//expect(res.send).toHaveBeenCalledWith(base64Decode(testPdf));
expect(res.send).toHaveBeenCalledWith(base64Decode('base64EncodedPDFstring'));
skjemabyggingproxyScope.done();
});

102 changes: 102 additions & 0 deletions packages/fyllut-backend/src/routers/api/forsteside-soknad.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { ForstesideRequestBody, forstesideUtils } from '@navikt/skjemadigitalisering-shared-domain';
import { readFileSync } from 'fs';
import nock from 'nock';
import path from 'path';
import { config } from '../../config/config';
import { mockNext, mockRequest, mockResponse } from '../../test/requestTestHelpers';
import forstesideAndSoknad from './forsteside-soknad';
import * as mottaksadresser from './mottaksadresser';

const { skjemabyggingProxyUrl, formsApiUrl } = config;

const addresses = [
{
_id: '6246de1afd03d2caeeda2825',
data: {
adresselinje1: 'Nav Arbeid og ytelser lønnsgaranti',
adresselinje2: 'Postboks 6683 St. Olavs Plass',
adresselinje3: '',
postnummer: '0129',
poststed: 'Oslo',
temakoder: 'FOS,HJE',
},
},
{
_id: '61c09f91ec962a0003c65014',
data: {
adresselinje1: 'Nav Skanning bidrag',
adresselinje2: 'PB 6215 Etterstad',
adresselinje3: '',
postnummer: '0603',
poststed: 'Oslo',
},
},
];

const formTitle = 'testskjema';
const filePathForsteside = path.join(process.cwd(), '/src/routers/api/test-forsteside.pdf');
const filePathSoknad = path.join(process.cwd(), '/src/routers/api/test-skjema.pdf');
const filePathMerged = path.join(process.cwd(), '/src/routers/api/test-merged.pdf');

describe('[endpoint] forsteside', () => {
beforeAll(() => {
vi.spyOn(mottaksadresser, 'loadMottaksadresser').mockImplementation(async () => addresses);
vi.spyOn(forstesideUtils, 'genererFoerstesideData').mockImplementation(
() =>
({
foerstesidetype: 'ETTERSENDELSE',
navSkjemaId: 'NAV 10.10.10',
spraakkode: 'NB',
overskriftstittel: 'Tittel',
arkivtittel: 'Tittel',
tema: 'HJE',
}) as ForstesideRequestBody,
);
});

it('Create front page', async () => {
const forstesidePdf = readFileSync(filePathForsteside);
const soknadPdf = readFileSync(filePathSoknad);
const mergedPdf = readFileSync(filePathMerged);
const encodedForstesidedPdf = forstesidePdf.toString('base64');
const encodedSoknadPdf = soknadPdf.toString('base64');

const recipientsMock = nock(formsApiUrl).get('/v1/recipients').reply(200, []);
const generateFileMock = nock(skjemabyggingProxyUrl!)
.post('/foersteside')
.reply(200, { foersteside: encodedForstesidedPdf });
const skjemabyggingproxyScope = nock(process.env.SKJEMABYGGING_PROXY_URL as string)
.post('/exstream')
.reply(200, { data: { result: [{ content: { data: encodedSoknadPdf } }] } });

const mergePdfScope = nock(process.env.GOTENBERG_URL as string)
.intercept('/forms/pdfengines/merge', 'POST', (body) => {
return body != null;
})
.reply(200, mergedPdf, { 'content-type': 'application/pdf' });

const req = mockRequest({
headers: {
AzureAccessToken: '',
},
body: {
form: JSON.stringify({
title: formTitle,
components: [],
properties: { mottaksadresseId: 'mottaksadresseId', path: '12345', skjemanummer: 'NAV 12.34-56' },
}),
submissionMethod: 'paper',
language: 'nb-NO',
submission: JSON.stringify({ data: {} }),
translations: JSON.stringify({}),
},
});

await forstesideAndSoknad.post(req, mockResponse(), mockNext());

expect(recipientsMock.isDone()).toBe(true);
expect(generateFileMock.isDone()).toBe(true);
expect(skjemabyggingproxyScope.isDone()).toBe(true);
expect(mergePdfScope.isDone()).toBe(true);
}, 10000);
});
129 changes: 129 additions & 0 deletions packages/fyllut-backend/src/routers/api/forsteside-soknad.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {
FormPropertiesType,
ForstesideRequestBody,
forstesideUtils,
Recipient,
} from '@navikt/skjemadigitalisering-shared-domain';
import { NextFunction, Request, Response } from 'express';
import correlator from 'express-correlation-id';
import fetch, { BodyInit, HeadersInit } from 'node-fetch';
import { config } from '../../config/config';
import { logger } from '../../logger';
import { base64Decode } from '../../utils/base64';
import { htmlResponseError, responseToError } from '../../utils/errorHandling.js';
import { logErrorWithStacktrace } from '../../utils/errors';
import { getPdf } from './exstream';
import { mergeFiles } from './helpers/gotenbergService';
//import { writeFileSync } from "node:fs";
//import path from "path";

const { skjemabyggingProxyUrl, formsApiUrl } = config;
//const filePath = path.join(process.cwd(), '/src/routers/api/merged.pdf');

const forstesideAndSoknad = {
post: async (req: Request, res: Response, next: NextFunction) => {
try {
const { form, submission, language, enhetNummer } = req.body;
const formParsed = JSON.parse(form);
const submissionParsed = JSON.parse(submission);
const submissionDataParsed = submissionParsed.data;
const recipients = await getRecipients(formParsed?.properties);

const forstesideBody = forstesideUtils.genererFoerstesideData(
formParsed,
submissionDataParsed,
language,
recipients,
enhetNummer,
);

const forstesideResponse: any = await forstesideRequest(req, JSON.stringify(forstesideBody));
logForsteside(req.body, forstesideResponse);

const forstesidePdf = base64Decode(forstesideResponse.foersteside);

const soknadResponse: any = await getPdf(req);

const soknadPdf = base64Decode(soknadResponse.data);

if (soknadPdf === undefined || forstesidePdf === undefined) {
throw htmlResponseError('Generering av førstesideark eller søknads PDF feilet');
}

const documents = [forstesidePdf, soknadPdf];

const mergedFile = await mergeFiles(
forstesideBody.navSkjemaId,
forstesideBody.overskriftstittel,
forstesideBody.spraakkode,
documents,
{ pdfa: true, pdfua: true },
);

//For PDF inspection
/*
writeFileSync(filePath, Buffer.from(mergedFile), {
flag: "w"
});
*/

const fileName = encodeURIComponent(`Førstesideark_${formParsed.path}.pdf`);

res.contentType('application/pdf');
res.setHeader('Content-Disposition', `inline; filename=${fileName}`);

res.send(Buffer.from(new Uint8Array(mergedFile)));
logger.info(`3. Returnert mergedFile`);
} catch (e) {
logErrorWithStacktrace(e as Error);
const forstesideError = htmlResponseError('Generering av førstesideark eller soknads PDF feilet');
next(forstesideError);
}
},
};

const getRecipients = async (formProperties: FormPropertiesType): Promise<Recipient[] | undefined> => {
if (formProperties.mottaksadresseId) {
const recipientsResponse = await fetch(`${formsApiUrl}/v1/recipients`, {
method: 'GET',
headers: {
'x-correlation-id': correlator.getId() as string,
},
});
if (!recipientsResponse.ok) {
throw new Error('Failed to fetch recipients');
}

return (await recipientsResponse.json()) as Recipient[] | undefined;
}
};

const forstesideRequest = async (req: Request, body?: BodyInit) => {
const response = await fetch(`${skjemabyggingProxyUrl}/foersteside`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${req.headers.AzureAccessToken}`,
'x-correlation-id': correlator.getId(),
} as HeadersInit,
body,
});

if (response.ok) {
return response.json();
}

throw await responseToError(response, 'Feil ved generering av førsteside', true);
};

const logForsteside = (forsteside: ForstesideRequestBody, response: any) => {
logger.info('Download frontpage', {
loepenummer: response.loepenummer,
navSkjemaId: forsteside.navSkjemaId,
tema: forsteside.tema,
enhetsnummer: forsteside.enhetsnummer,
spraakkode: forsteside.spraakkode,
});
};

export default forstesideAndSoknad;
41 changes: 4 additions & 37 deletions packages/fyllut-backend/src/routers/api/forstesideV2.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { ForstesideRequestBody, forstesideUtils } from '@navikt/skjemadigitalisering-shared-domain';
import { readFileSync } from 'fs';
import nock from 'nock';
import path from 'path';
import { config } from '../../config/config';
import { mockNext, mockRequest, mockResponse } from '../../test/requestTestHelpers';
import forstesideV2 from './forstesideV2';
@@ -33,11 +31,6 @@ const addresses = [
},
];

const formTitle = 'testskjema';
const filePathForsteside = path.join(process.cwd(), '/src/routers/api/test-forsteside.pdf');
const filePathSoknad = path.join(process.cwd(), '/src/routers/api/test-skjema.pdf');
const filePathMerged = path.join(process.cwd(), '/src/routers/api/test-merged.pdf');

describe('[endpoint] forsteside', () => {
beforeAll(() => {
vi.spyOn(mottaksadresser, 'loadMottaksadresser').mockImplementation(async () => addresses);
@@ -55,48 +48,22 @@ describe('[endpoint] forsteside', () => {
});

it('Create front page', async () => {
const forstesidePdf = readFileSync(filePathForsteside);
const soknadPdf = readFileSync(filePathSoknad);
const mergedPdf = readFileSync(filePathMerged);
const encodedForstesidedPdf = forstesidePdf.toString('base64');
const encodedSoknadPdf = soknadPdf.toString('base64');

const recipientsMock = nock(formsApiUrl).get('/v1/recipients').reply(200, []);
const generateFileMock = nock(skjemabyggingProxyUrl!)
.post('/foersteside')
.reply(200, { foersteside: encodedForstesidedPdf });
const skjemabyggingproxyScope = nock(process.env.SKJEMABYGGING_PROXY_URL as string)
.post('/exstream')
.reply(200, { data: { result: [{ content: { data: encodedSoknadPdf } }] } });

const mergePdfScope = nock(process.env.GOTENBERG_URL as string)
.intercept('/forms/pdfengines/merge', 'POST', (body) => {
return body != null;
})
.reply(200, mergedPdf, { 'content-type': 'application/pdf' });
const generateFileMock = nock(skjemabyggingProxyUrl!).post('/foersteside').reply(200, '{}');

const req = mockRequest({
headers: {
AzureAccessToken: '',
},
body: {
form: JSON.stringify({
title: formTitle,
components: [],
properties: { mottaksadresseId: 'mottaksadresseId', path: '12345', skjemanummer: 'NAV 12.34-56' },
}),
submissionMethod: 'paper',
language: 'nb-NO',
submission: JSON.stringify({ data: {} }),
translations: JSON.stringify({}),
form: JSON.stringify({ properties: { mottaksadresseId: 'mottaksadresseId' } }),
submissionData: '{}',
},
});

await forstesideV2.post(req, mockResponse(), mockNext());

expect(recipientsMock.isDone()).toBe(true);
expect(generateFileMock.isDone()).toBe(true);
expect(skjemabyggingproxyScope.isDone()).toBe(true);
expect(mergePdfScope.isDone()).toBe(true);
}, 10000);
});
});
51 changes: 8 additions & 43 deletions packages/fyllut-backend/src/routers/api/forstesideV2.ts
Original file line number Diff line number Diff line change
@@ -12,20 +12,16 @@ import { logger } from '../../logger';
import { base64Decode } from '../../utils/base64';
import { htmlResponseError, responseToError } from '../../utils/errorHandling.js';
import { logErrorWithStacktrace } from '../../utils/errors';
import { getPdf } from './exstream';
import { mergeFiles } from './helpers/gotenbergService';
//import { writeFileSync } from "node:fs";
//import path from "path";

const { skjemabyggingProxyUrl, formsApiUrl } = config;
//const filePath = path.join(process.cwd(), '/src/routers/api/merged.pdf');

const forstesideV2 = {
post: async (req: Request, res: Response, next: NextFunction) => {
try {
const { form, submission, language, enhetNummer } = req.body;
const { form, submissionData, language, enhetNummer } = req.body;
const formParsed = JSON.parse(form);
const submissionParsed = JSON.parse(submission);
const submissionDataParsed = submissionParsed.data;
const submissionDataParsed = JSON.parse(submissionData);

const recipients = await getRecipients(formParsed?.properties);

const forstesideBody = forstesideUtils.genererFoerstesideData(
@@ -36,47 +32,16 @@ const forstesideV2 = {
enhetNummer,
);

const forstesideResponse: any = await forstesideRequest(req, JSON.stringify(forstesideBody));
logForsteside(req.body, forstesideResponse);

const forstesidePdf = base64Decode(forstesideResponse.foersteside);

const soknadResponse: any = await getPdf(req);

const soknadPdf = base64Decode(soknadResponse.data);

if (soknadPdf === undefined || forstesidePdf === undefined) {
throw htmlResponseError('Generering av førstesideark eller søknads PDF feilet');
}

const documents = [forstesidePdf, soknadPdf];

const mergedFile = await mergeFiles(
forstesideBody.navSkjemaId,
forstesideBody.overskriftstittel,
forstesideBody.spraakkode,
documents,
{ pdfa: true, pdfua: true },
);

//For PDF inspection
/*
writeFileSync(filePath, Buffer.from(mergedFile), {
flag: "w"
});
*/
const response: any = await forstesideRequest(req, JSON.stringify(forstesideBody));
logForsteside(req.body, response);

const fileName = encodeURIComponent(`Førstesideark_${formParsed.path}.pdf`);

res.contentType('application/pdf');
res.setHeader('Content-Disposition', `inline; filename=${fileName}`);

res.send(Buffer.from(new Uint8Array(mergedFile)));
logger.info(`3. Returnert mergedFile`);
res.send(base64Decode(response.foersteside));
} catch (e) {
console.log(e);
logErrorWithStacktrace(e as Error);
const forstesideError = htmlResponseError('Generering av førstesideark eller soknads PDF feilet');
const forstesideError = htmlResponseError('Generering av førstesideark feilet');
next(forstesideError);
}
},
163 changes: 3 additions & 160 deletions packages/fyllut-backend/src/routers/api/helpers/gotenbergService.ts
Original file line number Diff line number Diff line change
@@ -1,138 +1,12 @@
import {
I18nTranslationMap,
I18nTranslationReplacements,
NavFormType,
Submission,
translationUtils,
} from '@navikt/skjemadigitalisering-shared-domain';
import correlator from 'express-correlation-id';
import FormData from 'form-data';
//import { readFileSync } from 'fs';
//import path from 'path';
import { config } from '../../../config/config';
import { logger } from '../../../logger';
import { appMetrics } from '../../../services';
import { synchronousResponseToError } from '../../../utils/errorHandling';
//import fetch from 'node-fetch';
import fetchWithRetry from '../../../utils/fetchWithRetry';
import { generateFooterHtml } from './footerBuilder';
import { createHtmlFromSubmission } from './htmlBuilder';

const { gotenbergUrl } = config;

export const createPdfAsByteArray = async (
accessToken: string,
form: NavFormType,
submission: Submission,
submissionMethod: string,
translations: I18nTranslationMap,
language: string,
) => {
return await buildHtmlAndConvertToPdf(accessToken, form, submission, submissionMethod, translations, language);
};

export const buildHtmlAndCreatePdf = async (
accessToken: string,
form: NavFormType,
submission: Submission,
submissionMethod: string,
translations: I18nTranslationMap,
language: string,
) => {
const pdf = await buildHtmlAndConvertToPdf(accessToken, form, submission, submissionMethod, translations, language);
return Buffer.from(new Uint8Array(pdf));
};

/* Feiler ved deploy: Error: ENOENT: no such file or directory, open '/home/runner/work/skjemabygging-formio/skjemabygging-formio/packages/fyllut-backend/dist/src/routers/api/helpers/nav-icon.svg'
const filePath = path.join(process.cwd(), '/src/routers/api/helpers/nav-icon.svg');
const navIcon = readFileSync(filePath, { encoding: 'utf-8', flag: 'r' });
*/
const navIcon = undefined;

export const buildHtmlAndConvertToPdf = async (
accessToken: string,
form: NavFormType,
submission: Submission,
submissionMethod: string,
translations: I18nTranslationMap,
language: string,
) => {
// Build Html from submission
const translate = (text: string, textReplacements?: I18nTranslationReplacements) =>
translationUtils.translateWithTextReplacements({
translations,
originalText: text,
params: textReplacements,
currentLanguage: language,
});

const html = createHtmlFromSubmission(form, submission, submissionMethod, translate, language);
if (!html || Object.keys(html).length === 0) {
throw Error('Missing HTML for generating PDF.');
}

// Build Footer
const { fodselsnummerDNummerSoker } = submission.data;
const footerHtml = generateFooterHtml(
(fodselsnummerDNummerSoker as string | undefined) || '—',
config.gitVersion,
form.properties.skjemanummer,
language,
translate,
);

appMetrics.exstreamPdfRequestsCounter.inc();
let errorOccurred = false;
const stopMetricRequestDuration = appMetrics.outgoingRequestDuration.startTimer({
service: 'gotenberg',
method: 'createPdfFromGotenberg',
});
const assets = {};
const options = { pdfa: true, pdfua: true };
try {
return await createPdfFromHtml(html, footerHtml, assets, options);
} catch (e) {
errorOccurred = true;
appMetrics.exstreamPdfFailuresCounter.inc();
throw e;
} finally {
const durationSeconds = stopMetricRequestDuration({ error: String(errorOccurred) });
logger.info(`Request to gotenberg pdf service completed after ${durationSeconds} seconds`, {
error: errorOccurred,
durationSeconds,
});
}
};

// Sette opp formdata med html og footer for å generere PDF v.hj.a Gotenberg.
const createPdfFromHtml = async (
html: string,
footer: string,
assets: { [filename: string]: string },
options: { pdfa: boolean; pdfua: boolean },
): Promise<any> => {
const formData = new FormData();

// Add the main HTML content
formData.append('files', Buffer.from(html), 'index.html');
formData.append('files', Buffer.from(footer), 'footer.html');
if (navIcon != null) {
formData.append('files', Buffer.from(navIcon), 'nav-logo.svg');
}

// Add optional assets like logos, footers, etc.
for (const [filename, content] of Object.entries(assets)) {
formData.append('files', Buffer.from(content), filename);
}

// Add Gotenberg-specific options
formData.append('pdfa', options.pdfa ? 'PDF/A-1b' : '');
formData.append('pdfua', options.pdfua ? 'true' : '');
formData.append('skipNetworkIdleEvent', 'false');

return await callGotenberg('/forms/chromium/convert/html', formData);
};

// Sette opp formdata til å merge en liste av PDFer
export const mergeFiles = async (
schema: string,
@@ -166,36 +40,17 @@ export const mergeFiles = async (
};
formData.append('metadata', `${JSON.stringify(metadata)}`);

// Hvordan sette språk?
// Hvordan sette språk? En engelsk og en norsk Gotenberg installasjon?
logger.info(`Skal kalle Gotenberg for å merge filer`);
return await callGotenberg('/forms/pdfengines/merge', formData);
//const mergedPdf = await callGotenberg("/forms/pdfengines/merge", formData);
//return await applyMetadata(mergedPdf, metadata, options);
};

const formatPDFDate = (date: Date) => {
return date.toISOString().replace(/\.\d{3}Z$/, ''); // Removes milliseconds & Zulu time
};

/* For future use
const applyMetadata = async (
pdfBuffer: ArrayBuffer,
metadata: Record<string, string>,
options: { pdfa: boolean; pdfua: boolean },
) => {
const formData = new FormData();
formData.append("file", Buffer.from(new Uint8Array(pdfBuffer)), "test-merged.pdf");
formData.append("metadata", JSON.stringify(metadata));
formData.append('pdfa', options.pdfa ? 'PDF/A-1b' : '');
formData.append('pdfua', options.pdfua ? 'true' : '');
return callGotenberg('/forms/pdfengines/convert', formData);
};
*/

// Generisk metode for kall til mot Gotenberg gitt rute og preparert FormData
const callGotenberg = async (route: string, formData: FormData): Promise<any> => {
export const callGotenberg = async (route: string, formData: FormData): Promise<any> => {
console.log(`Calling Gotenberg with url = ${gotenbergUrl}${route}`);

try {
@@ -204,24 +59,12 @@ const callGotenberg = async (route: string, formData: FormData): Promise<any> =>
retry: 1,
headers: {
accept: 'application/pdf, text/plain',
correlation_id: 'xx',
correlation_id: correlator.getId(),
} as HeadersInit,
method: 'POST',
body: formData,
});

/*
const gotenbergResponse = await fetch(`${gotenbergUrl}/forms/chromium/convert/html`, {
method: 'POST',
headers: {
...formData.getHeaders(),
contentType: 'multipart/form-data',
accept: 'application/pdf, text/plain',
} as HeadersInit,
body: formData,
});
*/

if (!gotenbergResponse.ok) {
console.log(`Response fra Gotenberg feilet`);
const errorText = await gotenbergResponse.text();
Original file line number Diff line number Diff line change
@@ -37,7 +37,6 @@ export const createPdf = async (
translations: I18nTranslationMap,
language: string,
) => {
logger.info('createPdf: Start');
const translate = (text: string, textReplacements?: I18nTranslationReplacements) =>
translationUtils.translateWithTextReplacements({
translations,
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import {
I18nTranslationMap,
I18nTranslationReplacements,
NavFormType,
Submission,
translationUtils,
} from '@navikt/skjemadigitalisering-shared-domain';
import FormData from 'form-data';
//import { readFileSync } from 'fs';
//import path from 'path';
import { config } from '../../../config/config';
import { logger } from '../../../logger';
import { appMetrics } from '../../../services';
import { callGotenberg } from './gotenbergService';
//import { synchronousResponseToError } from '../../../utils/errorHandling';
//import fetch from 'node-fetch';
import { generateFooterHtml } from './footerBuilder';
import { createHtmlFromSubmission } from './htmlBuilder';

export const createPdfAsByteArray = async (
accessToken: string,
form: NavFormType,
submission: Submission,
submissionMethod: string,
translations: I18nTranslationMap,
language: string,
) => {
return await buildHtmlAndConvertToPdf(accessToken, form, submission, submissionMethod, translations, language);
};

export const buildHtmlAndCreatePdf = async (
accessToken: string,
form: NavFormType,
submission: Submission,
submissionMethod: string,
translations: I18nTranslationMap,
language: string,
) => {
const pdf = await buildHtmlAndConvertToPdf(accessToken, form, submission, submissionMethod, translations, language);
return Buffer.from(new Uint8Array(pdf));
};

/* Feiler ved deploy: Error: ENOENT: no such file or directory, open '/home/runner/work/skjemabygging-formio/skjemabygging-formio/packages/fyllut-backend/dist/src/routers/api/helpers/nav-icon.svg'
const filePath = path.join(process.cwd(), '/src/routers/api/helpers/nav-icon.svg');
const navIcon = readFileSync(filePath, { encoding: 'utf-8', flag: 'r' });
*/
const navIcon = undefined;

const buildHtmlAndConvertToPdf = async (
accessToken: string,
form: NavFormType,
submission: Submission,
submissionMethod: string,
translations: I18nTranslationMap,
language: string,
) => {
// Build Html from submission
const translate = (text: string, textReplacements?: I18nTranslationReplacements) =>
translationUtils.translateWithTextReplacements({
translations,
originalText: text,
params: textReplacements,
currentLanguage: language,
});

const html = createHtmlFromSubmission(form, submission, submissionMethod, translate, language);
if (!html || Object.keys(html).length === 0) {
throw Error('Missing HTML for generating PDF.');
}

// Build Footer
const { fodselsnummerDNummerSoker } = submission.data;
const footerHtml = generateFooterHtml(
(fodselsnummerDNummerSoker as string | undefined) || '—',
config.gitVersion,
form.properties.skjemanummer,
language,
translate,
);

appMetrics.exstreamPdfRequestsCounter.inc();
let errorOccurred = false;
const stopMetricRequestDuration = appMetrics.outgoingRequestDuration.startTimer({
service: 'gotenberg',
method: 'createPdfFromGotenberg',
});
const assets = {};
const options = { pdfa: true, pdfua: true };
try {
return await createPdfFromHtml(html, footerHtml, assets, options);
} catch (e) {
errorOccurred = true;
appMetrics.exstreamPdfFailuresCounter.inc();
throw e;
} finally {
const durationSeconds = stopMetricRequestDuration({ error: String(errorOccurred) });
logger.info(`Request to gotenberg pdf service completed after ${durationSeconds} seconds`, {
error: errorOccurred,
durationSeconds,
});
}
};

// Sette opp formdata med html og footer for å generere PDF v.hj.a Gotenberg.
const createPdfFromHtml = async (
html: string,
footer: string,
assets: { [filename: string]: string },
options: { pdfa: boolean; pdfua: boolean },
): Promise<any> => {
const formData = new FormData();

// Add the main HTML content
formData.append('files', Buffer.from(html), 'index.html');
formData.append('files', Buffer.from(footer), 'footer.html');
if (navIcon != null) {
formData.append('files', Buffer.from(navIcon), 'nav-logo.svg');
}

// Add optional assets like logos, footers, etc.
for (const [filename, content] of Object.entries(assets)) {
formData.append('files', Buffer.from(content), filename);
}

// Add Gotenberg-specific options
formData.append('pdfa', options.pdfa ? 'PDF/A-2b' : '');
formData.append('pdfua', options.pdfua ? 'true' : '');
formData.append('skipNetworkIdleEvent', 'false');

return await callGotenberg('/forms/chromium/convert/html', formData);
};
2 changes: 2 additions & 0 deletions packages/fyllut-backend/src/routers/api/index.js
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ import exstream from './exstream';
import form from './form';
import forms from './forms';
import forsteside from './forsteside';
import forstesideAndSoknad from './forsteside-soknad';
import globalTranslations from './global-translations.js';
import log from './log';
import mottaksadresser from './mottaksadresser.js';
@@ -36,6 +37,7 @@ apiRouter.get('/enhetsliste', enhetsliste.get);
apiRouter.get('/forms', tryCatch(forms.get));
apiRouter.get('/forms/:formPath', tryCatch(form.get));
apiRouter.post('/foersteside', azureSkjemabyggingProxy, forsteside.post);
apiRouter.post('/foersteside-soknad', azureSkjemabyggingProxy, forstesideAndSoknad.post);
apiRouter.get('/global-translations/:languageCode', tryCatch(globalTranslations.get));
apiRouter.get('/translations/:form', tryCatch(translations.get));
apiRouter.get('/mottaksadresser', tryCatch(mottaksadresser.get));
Original file line number Diff line number Diff line change
@@ -48,9 +48,9 @@ const LetterDownload = ({ form, index, submission, enhetsListe, fyllutBaseURL, t

language: currentLanguage,
enhetNummer: selectedEnhetNummer,
version: 'v2',
version: 'v3',
}}
actionUrl={`${fyllutBaseURL}/api/foersteside`}
actionUrl={`${fyllutBaseURL}/api/foersteside-soknad`}
label={translate(TEXTS.grensesnitt.prepareLetterPage.downloadCoverPage)}
onSubmit={(event) => {
if (enhetsListe.length > 0 && !selectedEnhetNummer) {