diff --git a/sci-log-db/src/__tests__/unit/service.export-snippet.unit.ts b/sci-log-db/src/__tests__/unit/service.export-snippet.unit.ts index bb2a5f6a..53239557 100644 --- a/sci-log-db/src/__tests__/unit/service.export-snippet.unit.ts +++ b/sci-log-db/src/__tests__/unit/service.export-snippet.unit.ts @@ -240,9 +240,13 @@ describe('Export service unit', function (this: Suite) { const exportFile = await exportService['exportToPdf']( paragraphs, {exportFile: 'dir/file.pdf', exportDir: 'dir'}, + {authorization: 'Bearer XXXX'}, 'aTitle', ); expect(addTitle.callCount).to.be.eql(1); + expect(exportService.authorizationHeader).to.be.eql({ + authorization: 'Bearer XXXX', + }); expect(htmlToPDF.callCount).to.be.eql(o); expect(exportFile).to.be.eql('dir/file.pdf'); }); @@ -341,6 +345,26 @@ describe('Export service unit', function (this: Suite) { [['someFile_>_.pdf', 'accessHash2']], ], }, + { + input: [ + {files: []}, + '', + ], + expected: [ + 'attachments/someFile.pdf', + [['someFile.pdf', '111fd07c4f1f010a51e32b36']], + ], + }, + { + input: [ + {files: []}, + '', + ], + expected: [ + '', + [], + ], + }, ].forEach((t, i) => { it(`attachment ${i}`, async () => { const element = textContentToHTML({textcontent: t.input[1]} as Paragraph); @@ -357,12 +381,15 @@ describe('Export service unit', function (this: Suite) { // eslint-disable-next-line @typescript-eslint/no-explicit-any .resolves({arrayBuffer: async () => '' as any} as Response); sandbox.stub(Buffer, 'from'); + exportService.authorizationHeader = {authorization: 'Bearer XXXX'}; const writeFileSpy = sandbox.stub(fspromise, 'writeFile'); await exportService['downloadAttachment']('someDir', ['a', 'b']); expect(writeFileSpy.calledWith('someDir/a', match.any)).to.be.eql(true); - expect(responseSpy.calledWith('http://localhost:3000/images/b')).to.be.eql( - true, - ); + expect( + responseSpy.calledWith('http://localhost:3000/images/b', { + headers: {authorization: 'Bearer XXXX'}, + }), + ).to.be.eql(true); }); [ @@ -394,4 +421,34 @@ describe('Export service unit', function (this: Suite) { ).to.eql(t.expected[2]); }); }); + + [ + {input: [null], expected: undefined}, + {input: ['some'], expected: undefined}, + {input: ['https://abc'], expected: undefined}, + {input: ['http://abc'], expected: undefined}, + {input: ['http://abc/download'], expected: undefined}, + {input: ['http://abc/download/123'], expected: undefined}, + { + input: ['http://abc/download/111fd07c4f1f010a51e32b36'], + expected: '111fd07c4f1f010a51e32b36', + }, + {input: ['file:somefile', []], expected: undefined}, + { + input: [ + 'file:somefile', + [{fileHash: 'somefile', accessHash: 'accessHash'}], + ], + expected: 'accessHash', + }, + ].forEach((t, i) => { + it(`attachmentDownloadUrl ${i}`, async () => { + expect( + exportService['attachmentDownloadUrl']( + t.input[0] as string | null, + t.input[1] as {fileHash?: string; accessHash?: string}[] | undefined, + ), + ).to.eql(t.expected); + }); + }); }); diff --git a/sci-log-db/src/mixins/basesnippet.repository-mixin.ts b/sci-log-db/src/mixins/basesnippet.repository-mixin.ts index af45535e..7d44d808 100644 --- a/sci-log-db/src/mixins/basesnippet.repository-mixin.ts +++ b/sci-log-db/src/mixins/basesnippet.repository-mixin.ts @@ -452,6 +452,7 @@ function ExportRepositoryMixin< const outFile = await exportService.exportToPdf( snippets as unknown as Paragraph[], {exportFile, exportDir}, + _.pick(response?.req?.headers, 'authorization'), parentName, ); response.download(outFile, (err, path = exportDir) => { diff --git a/sci-log-db/src/services/export-snippets.service.ts b/sci-log-db/src/services/export-snippets.service.ts index fa252122..dd58e202 100644 --- a/sci-log-db/src/services/export-snippets.service.ts +++ b/sci-log-db/src/services/export-snippets.service.ts @@ -36,6 +36,7 @@ export class ExportService { subsnippetCounter: number; attachments: string[][] = []; attachmentsFolder = 'attachments'; + authorizationHeader: {authorization?: string}; constructor( @inject(RestBindings.SERVER) @@ -228,20 +229,40 @@ export class ExportService { }; private attachment = (snippet: Paragraph, element: Element) => { - snippet.files?.map(fileSnippet => { - const fileLinkElement = element.querySelector( - `.fileLink[href='file:${fileSnippet.fileHash}']`, - ); - if (!fileLinkElement) return; + element.querySelectorAll('.fileLink').forEach(fileLinkElement => { + const href = fileLinkElement.getAttribute('href'); + const downloadUrl = this.attachmentDownloadUrl(href, snippet.files); + if (!downloadUrl) return; const attachment = fileLinkElement.textContent ?? ''; const attachmentElement = this.document.createElement('fileLink'); attachmentElement.innerHTML = `${this.attachmentsFolder}/${fileLinkElement.innerHTML}`; fileLinkElement.replaceWith(attachmentElement); - this.attachments.push([attachment, fileSnippet.accessHash as string]); + this.attachments.push([attachment, downloadUrl]); }); return element; }; + private attachmentDownloadUrl( + href: string | null, + files: {fileHash?: string; accessHash?: string}[] | undefined, + ) { + let accessHash: string | undefined; + if (href?.startsWith('https://') || href?.startsWith('http://')) { + const objectId = require('mongodb').ObjectId; + const hrefParts = href.split('/'); + const fileId = hrefParts.pop(); + if (objectId.isValid(fileId) && hrefParts.pop() === 'download') + return fileId; + return; + } else if ( + files?.some( + f => `file:${f.fileHash}` === href && (accessHash = f.accessHash), + ) + ) + return accessHash; + return; + } + private countSnippets(linkType?: LinkType) { let counter: number | string = ''; if (linkType === 'paragraph') { @@ -264,8 +285,10 @@ export class ExportService { async exportToPdf( snippets: Paragraph[], exportPath: {exportFile: string; exportDir: string}, + authorizationHeader: {authorization?: string}, title?: string, ) { + this.authorizationHeader = authorizationHeader; const browser = await puppeteerLaunc({ executablePath: process.env.CHROME_BIN, args: ['--no-sandbox'], @@ -320,7 +343,9 @@ export class ExportService { attachmentDir: string, attachment: string[], ) { - const response = await fetch(`${this.server.url}/images/${attachment[1]}`); + const response = await fetch(`${this.server.url}/images/${attachment[1]}`, { + headers: this.authorizationHeader, + }); const buffer = Buffer.from(await response.arrayBuffer()); const destinationFile = `${attachmentDir}/${attachment[0]}`; await writeFile(destinationFile, buffer);