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.
This commit is contained in:
Loïs Mansot 2023-10-18 16:47:12 +02:00
parent 15b51a56a1
commit 0728559e0e
No known key found for this signature in database
GPG Key ID: 8CF1F4150DDA726D
3 changed files with 30 additions and 8 deletions

View File

@ -45,6 +45,7 @@
"@mailchimp/mailchimp_transactional": "^1.0.50", "@mailchimp/mailchimp_transactional": "^1.0.50",
"@pinata/sdk": "^2.1.0", "@pinata/sdk": "^2.1.0",
"@prisma/client": "^4.11.0", "@prisma/client": "^4.11.0",
"adm-zip": "^0.5.10",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.0", "class-validator": "^0.14.0",
"classnames": "^2.3.2", "classnames": "^2.3.2",
@ -71,6 +72,7 @@
"uuidv4": "^6.2.13" "uuidv4": "^6.2.13"
}, },
"devDependencies": { "devDependencies": {
"@types/adm-zip": "^0.5.3",
"@types/cors": "^2.8.13", "@types/cors": "^2.8.13",
"@types/cron": "^2.0.1", "@types/cron": "^2.0.1",
"@types/express": "^4.17.16", "@types/express": "^4.17.16",

View File

@ -3,9 +3,10 @@ import { Controller, Get, Post } from "@ControllerPattern/index";
import ApiController from "@Common/system/controller-pattern/ApiController"; import ApiController from "@Common/system/controller-pattern/ApiController";
import { Service } from "typedi"; import { Service } from "typedi";
import { Document, OfficeFolder } from "le-coffre-resources/dist/Notary"; 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 OfficeFoldersService from "@Services/notary/OfficeFoldersService/OfficeFoldersService";
import OfficeFolderAnchorsRepository from "@Repositories/OfficeFolderAnchorsRepository"; import OfficeFolderAnchorsRepository from "@Repositories/OfficeFolderAnchorsRepository";
import FilesService from "@Services/common/FilesService/FilesService";
import SecureService from "@Services/common/SecureService/SecureService"; import SecureService from "@Services/common/SecureService/SecureService";
import authHandler from "@App/middlewares/AuthHandler"; import authHandler from "@App/middlewares/AuthHandler";
import ruleHandler from "@App/middlewares/RulesHandler"; 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 NotificationBuilder from "@Common/notifications/NotificationBuilder";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import OfficeFolderAnchorsService from "@Services/notary/OfficeFolderAnchorsService/OfficeFolderAnchorsService"; import OfficeFolderAnchorsService from "@Services/notary/OfficeFolderAnchorsService/OfficeFolderAnchorsService";
import Zip from "adm-zip";
const hydrateOfficeFolderAnchor = (data: any): OfficeFolderAnchor => const hydrateOfficeFolderAnchor = (data: any): OfficeFolderAnchor =>
OfficeFolderAnchor.hydrate<OfficeFolderAnchor>( OfficeFolderAnchor.hydrate<OfficeFolderAnchor>(
@ -41,13 +43,14 @@ export default class OfficeFoldersController extends ApiController {
private officeFolderAnchorsRepository: OfficeFolderAnchorsRepository, private officeFolderAnchorsRepository: OfficeFolderAnchorsRepository,
private officeFolderAnchorsService: OfficeFolderAnchorsService, private officeFolderAnchorsService: OfficeFolderAnchorsService,
private officeFoldersService: OfficeFoldersService, private officeFoldersService: OfficeFoldersService,
private filesService: FilesService,
private notificationBuilder: NotificationBuilder, private notificationBuilder: NotificationBuilder,
) { ) {
super(); 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]) @Get("/api/v1/notary/anchors/download/:uid", [authHandler, ruleHandler, folderHandler])
protected async download(req: Request, response: Response) { protected async download(req: Request, response: Response) {
@ -76,6 +79,7 @@ export default class OfficeFoldersController extends ApiController {
const officeFolder = OfficeFolder.hydrate<OfficeFolder>(officeFolderFound, { strategy: "excludeAll" }); const officeFolder = OfficeFolder.hydrate<OfficeFolder>(officeFolderFound, { strategy: "excludeAll" });
const folderHashes = getFolderHashes(officeFolder); const folderHashes = getFolderHashes(officeFolder);
const folderFilesUid = getFolderFilesUid(officeFolder);
if (folderHashes.length === 0) { if (folderHashes.length === 0) {
this.httpNotFoundRequest(response, "No file hash to anchor"); this.httpNotFoundRequest(response, "No file hash to anchor");
@ -83,10 +87,25 @@ export default class OfficeFoldersController extends ApiController {
} }
const sortedHashes = [...folderHashes].sort(); const sortedHashes = [...folderHashes].sort();
const buffer = await this.secureService.download(sortedHashes); const anchoringProof = await this.secureService.download(sortedHashes);
response.setHeader("Content-Type", "application/pdf"); const addFileToZip = (zip: Zip) => (uid: string): Promise<void> =>
this.httpSuccess(response, buffer); (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) { } catch (error) {
this.httpInternalError(response, error); this.httpInternalError(response, error);
return; return;

View File

@ -10,7 +10,7 @@ import { Document, File, OfficeFolder } from "le-coffre-resources/dist/Notary";
export const folderDocumentsLens = Optics.Lens.fromNullableProp<OfficeFolder>()("documents", []); export const folderDocumentsLens = Optics.Lens.fromNullableProp<OfficeFolder>()("documents", []);
export const documentFilesLens = Optics.Lens.fromNullableProp<Document>()("files", []); export const documentFilesLens = Optics.Lens.fromNullableProp<Document>()("files", []);
export const fileHashLens = Optics.Lens.fromProp<File>()("hash"); export const fileHashLens = Optics.Lens.fromProp<File>()("hash");
export const filePathLens = Optics.Lens.fromProp<File>()("file_path"); export const fileUidLens = Optics.Lens.fromProp<File>()("uid");
/** /**
* Traversals * Traversals
@ -24,10 +24,11 @@ export const folderFilesTraversal = folderDocumentsLens
.composeTraversal(filesTraversal); .composeTraversal(filesTraversal);
export const folderHashesTraversal = folderFilesTraversal.composeLens(fileHashLens); export const folderHashesTraversal = folderFilesTraversal.composeLens(fileHashLens);
export const folderFilesPathTraversal = folderFilesTraversal.composeLens(filePathLens); export const folderFilesUidTraversal = folderFilesTraversal.composeLens(fileUidLens);
/** /**
* Getters * Getters
*/ */
export const getFolderFiles = (folder: OfficeFolder) => Traversal.getAll(folder)(folderFilesTraversal);
export const getFolderHashes = (folder: OfficeFolder) => Traversal.getAll(folder)(folderHashesTraversal); 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);