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);