From 0728559e0ec16563309a482b29b3a64df532f3a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFs=20Mansot?= <26844641+devfull@users.noreply.github.com> Date: Wed, 18 Oct 2023 16:47:12 +0200 Subject: [PATCH] update `anchors/download` route to get a complete zip archive The zip will contain: - the anchoring proof PDF ; - plus all accessible files stored on IPFS. If the key is invalid, a broken file will be added to the archive. If the key is missing, the file will not be added to the archive, because there is no way it could be deciphered. For now, all network errors from IPFS are silently ignored, resulting in an archive containing only the anchoring proof. --- package.json | 2 ++ .../notary/OfficeFolderAnchorsController.ts | 29 +++++++++++++++---- src/common/optics/notary/index.ts | 7 +++-- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 93cd586e..0c161e19 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@mailchimp/mailchimp_transactional": "^1.0.50", "@pinata/sdk": "^2.1.0", "@prisma/client": "^4.11.0", + "adm-zip": "^0.5.10", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "classnames": "^2.3.2", @@ -71,6 +72,7 @@ "uuidv4": "^6.2.13" }, "devDependencies": { + "@types/adm-zip": "^0.5.3", "@types/cors": "^2.8.13", "@types/cron": "^2.0.1", "@types/express": "^4.17.16", diff --git a/src/app/api/notary/OfficeFolderAnchorsController.ts b/src/app/api/notary/OfficeFolderAnchorsController.ts index 3a43a703..c5a8a65b 100644 --- a/src/app/api/notary/OfficeFolderAnchorsController.ts +++ b/src/app/api/notary/OfficeFolderAnchorsController.ts @@ -3,9 +3,10 @@ import { Controller, Get, Post } from "@ControllerPattern/index"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Service } from "typedi"; import { Document, OfficeFolder } from "le-coffre-resources/dist/Notary"; -import { getFolderHashes } from "@Common/optics/notary"; +import { getFolderHashes, getFolderFilesUid } from "@Common/optics/notary"; import OfficeFoldersService from "@Services/notary/OfficeFoldersService/OfficeFoldersService"; import OfficeFolderAnchorsRepository from "@Repositories/OfficeFolderAnchorsRepository"; +import FilesService from "@Services/common/FilesService/FilesService"; import SecureService from "@Services/common/SecureService/SecureService"; import authHandler from "@App/middlewares/AuthHandler"; import ruleHandler from "@App/middlewares/RulesHandler"; @@ -14,6 +15,7 @@ import OfficeFolderAnchor from "le-coffre-resources/dist/Notary/OfficeFolderAnch import NotificationBuilder from "@Common/notifications/NotificationBuilder"; import { Prisma } from "@prisma/client"; import OfficeFolderAnchorsService from "@Services/notary/OfficeFolderAnchorsService/OfficeFolderAnchorsService"; +import Zip from "adm-zip"; const hydrateOfficeFolderAnchor = (data: any): OfficeFolderAnchor => OfficeFolderAnchor.hydrate( @@ -41,13 +43,14 @@ export default class OfficeFoldersController extends ApiController { private officeFolderAnchorsRepository: OfficeFolderAnchorsRepository, private officeFolderAnchorsService: OfficeFolderAnchorsService, private officeFoldersService: OfficeFoldersService, + private filesService: FilesService, private notificationBuilder: NotificationBuilder, ) { super(); } /** - * @description Download a folder anchoring proof document + * @description Download a folder anchoring proof document along with all accessible files */ @Get("/api/v1/notary/anchors/download/:uid", [authHandler, ruleHandler, folderHandler]) protected async download(req: Request, response: Response) { @@ -76,6 +79,7 @@ export default class OfficeFoldersController extends ApiController { const officeFolder = OfficeFolder.hydrate(officeFolderFound, { strategy: "excludeAll" }); const folderHashes = getFolderHashes(officeFolder); + const folderFilesUid = getFolderFilesUid(officeFolder); if (folderHashes.length === 0) { this.httpNotFoundRequest(response, "No file hash to anchor"); @@ -83,10 +87,25 @@ export default class OfficeFoldersController extends ApiController { } const sortedHashes = [...folderHashes].sort(); - const buffer = await this.secureService.download(sortedHashes); + const anchoringProof = await this.secureService.download(sortedHashes); - response.setHeader("Content-Type", "application/pdf"); - this.httpSuccess(response, buffer); + const addFileToZip = (zip: Zip) => (uid: string): Promise => + (async () => { + const data = await this.filesService.download(uid); + if (!data?.buffer) return; + zip.addFile(`${uid}-${data.file.file_name}`, data.buffer); + })() + + const uids: string[] = folderFilesUid.filter((uid): uid is string => uid !== undefined); + + const zip = new Zip(); + zip.addFile(`${uid}-certificat-de-dépôt.pdf`, anchoringProof); + await Promise.allSettled( + uids.map(addFileToZip(zip)) + ) + + response.setHeader("Content-Type", "application/zip"); + this.httpSuccess(response, zip.toBuffer()); } catch (error) { this.httpInternalError(response, error); return; diff --git a/src/common/optics/notary/index.ts b/src/common/optics/notary/index.ts index 1ee49646..ae7a4587 100644 --- a/src/common/optics/notary/index.ts +++ b/src/common/optics/notary/index.ts @@ -10,7 +10,7 @@ import { Document, File, OfficeFolder } from "le-coffre-resources/dist/Notary"; export const folderDocumentsLens = Optics.Lens.fromNullableProp()("documents", []); export const documentFilesLens = Optics.Lens.fromNullableProp()("files", []); export const fileHashLens = Optics.Lens.fromProp()("hash"); -export const filePathLens = Optics.Lens.fromProp()("file_path"); +export const fileUidLens = Optics.Lens.fromProp()("uid"); /** * Traversals @@ -24,10 +24,11 @@ export const folderFilesTraversal = folderDocumentsLens .composeTraversal(filesTraversal); export const folderHashesTraversal = folderFilesTraversal.composeLens(fileHashLens); -export const folderFilesPathTraversal = folderFilesTraversal.composeLens(filePathLens); +export const folderFilesUidTraversal = folderFilesTraversal.composeLens(fileUidLens); /** * Getters */ +export const getFolderFiles = (folder: OfficeFolder) => Traversal.getAll(folder)(folderFilesTraversal); export const getFolderHashes = (folder: OfficeFolder) => Traversal.getAll(folder)(folderHashesTraversal); -export const getFolderFilesPath = (folder: OfficeFolder) => Traversal.getAll(folder)(folderFilesPathTraversal); +export const getFolderFilesUid = (folder: OfficeFolder) => Traversal.getAll(folder)(folderFilesUidTraversal);