From 5bd5e096cac95fbe38c1b1695bc48035c6b130e6 Mon Sep 17 00:00:00 2001 From: Max S Date: Tue, 10 Sep 2024 18:11:10 +0200 Subject: [PATCH] add filesNotaryController download route --- src/app/api/notary/CustomersController.ts | 5 - .../api/notary/DocumentsNotaryController.ts | 36 +++--- src/app/api/notary/FilesController.ts | 6 +- src/app/api/notary/FilesNotaryController.ts | 109 ++++++++++++++++++ src/app/index.ts | 6 +- .../FilesNotaryHandler.ts | 48 ++++++++ .../common/FilesNotaryService/FilesService.ts | 90 +++++++++++++++ .../common/FilesService/FilesService.ts | 35 +----- 8 files changed, 275 insertions(+), 60 deletions(-) create mode 100644 src/app/api/notary/FilesNotaryController.ts create mode 100644 src/app/middlewares/OfficeMembershipHandlers/FilesNotaryHandler.ts create mode 100644 src/services/common/FilesNotaryService/FilesService.ts diff --git a/src/app/api/notary/CustomersController.ts b/src/app/api/notary/CustomersController.ts index 2df0e275..8dc1e1c9 100644 --- a/src/app/api/notary/CustomersController.ts +++ b/src/app/api/notary/CustomersController.ts @@ -238,9 +238,7 @@ export default class CustomersController extends ApiController { const documentEntities : Documents[] = []; //For each document uid, use DocumentsService.getByUid to get the document entity and add it to the documents array for (const documentUid of documentsUid) { - console.log("documentUid", documentUid); const documentEntity = await this.documentsService.getByUid(documentUid, { document_type : true, folder : true }); - console.log("documentEntity", documentEntity); if (!documentEntity) { this.httpBadRequest(response, "Document not found"); @@ -250,7 +248,6 @@ export default class CustomersController extends ApiController { } const customerEntity = await this.customersService.getByUid(uid, { contact: true, office : true }); - console.log("customerEntity", customerEntity); if (!customerEntity) { @@ -260,13 +257,11 @@ export default class CustomersController extends ApiController { //Hydrate ressource with prisma entity const customer = Customer.hydrate(customerEntity, { strategy: "excludeAll" }); - console.log("customer", customer); // Call service to send reminder with documents const reminder = await this.customersService.sendDocumentsReminder(customer, documentEntities); - console.log("Reminder sent", reminder); // Success diff --git a/src/app/api/notary/DocumentsNotaryController.ts b/src/app/api/notary/DocumentsNotaryController.ts index afcd826e..56ea9cdf 100644 --- a/src/app/api/notary/DocumentsNotaryController.ts +++ b/src/app/api/notary/DocumentsNotaryController.ts @@ -1,16 +1,15 @@ -import { Response, Request } from "express"; -import { Controller, Delete, Get, Post } from "@ControllerPattern/index"; -import ApiController from "@Common/system/controller-pattern/ApiController"; -import { Service } from "typedi"; -import DocumentsNotaryService from "@Services/notary/DocumentsNotaryService/DocumentsNotaryService"; -import { DocumentsNotary, Prisma } from "@prisma/client"; -import { DocumentNotary, FileNotary } from "le-coffre-resources/dist/Notary"; import authHandler from "@App/middlewares/AuthHandler"; -import OfficeFoldersService from "@Services/notary/OfficeFoldersService/OfficeFoldersService"; +import ApiController from "@Common/system/controller-pattern/ApiController"; +import { Controller, Delete, Get, Post } from "@ControllerPattern/index"; +import { DocumentsNotary, Prisma } from "@prisma/client"; import CustomersService from "@Services/admin/CustomersService/CustomersService"; +import FilesNotaryService from "@Services/common/FilesNotaryService/FilesService"; +import DocumentsNotaryService from "@Services/notary/DocumentsNotaryService/DocumentsNotaryService"; +import OfficeFoldersService from "@Services/notary/OfficeFoldersService/OfficeFoldersService"; import UsersService from "@Services/notary/UsersService/UsersService"; -import FilesService from "@Services/common/FilesService/FilesService"; -// import NotificationBuilder from "@Common/notifications/NotificationBuilder"; +import { Request, Response } from "express"; +import { DocumentNotary, FileNotary } from "le-coffre-resources/dist/Notary"; +import { Service } from "typedi"; @Controller() @Service() @@ -20,7 +19,7 @@ export default class DocumentsNotaryController extends ApiController { private officeFoldersService: OfficeFoldersService, private customerService: CustomersService, private userService: UsersService, - private filesService: FilesService, + private filesNotaryService: FilesNotaryService, ) { super(); } @@ -63,17 +62,13 @@ export default class DocumentsNotaryController extends ApiController { @Post("/api/v1/notary/documents_notary", [authHandler]) protected async post(req: Request, response: Response) { try { - console.log("req.file", req.file); - - if(!req.file) return; + if (!req.file) return; const customer = await this.customerService.getByUid(req.body.customerUid); if (!customer) return; - console.log("customer", customer); const folder = await this.officeFoldersService.getByUid(req.body.folderUid); if (!folder) return; - console.log("folder", folder); const user = await this.userService.getByUid(req.body.user.userId); if (!user) return; @@ -82,20 +77,19 @@ export default class DocumentsNotaryController extends ApiController { customer: customer, folder: folder, depositor: user, - }); + }); const documentNotaryEntityCreated = await this.documentsNotaryService.create(documentNotaryEntity); - console.log("documentNotaryEntityCreated", documentNotaryEntityCreated); const query = JSON.stringify({ document: { uid: documentNotaryEntityCreated.uid } }); const fileEntity = FileNotary.hydrate(JSON.parse(query)); - const fileEntityCreated = await this.filesService.createFileNotary(fileEntity, req.file!); - if(!fileEntityCreated) { + const fileEntityCreated = await this.filesNotaryService.create(fileEntity, req.file!); + if (!fileEntityCreated) { this.httpBadRequest(response, "File could not be created"); return; - }; + } const documentNotary = await this.documentsNotaryService.getByUid(documentNotaryEntityCreated.uid); diff --git a/src/app/api/notary/FilesController.ts b/src/app/api/notary/FilesController.ts index 46a4969b..a630218c 100644 --- a/src/app/api/notary/FilesController.ts +++ b/src/app/api/notary/FilesController.ts @@ -27,14 +27,14 @@ export default class FilesController extends ApiController { let query: Prisma.FilesFindManyArgs = {}; if (req.query["q"]) { query = JSON.parse(req.query["q"] as string); - if(query.where?.uid) { + if (query.where?.uid) { this.httpBadRequest(response, "You can't filter by uid"); return; } } const officeId: string = req.body.user.office_Id; - const officeWhereInput: Prisma.OfficesWhereInput = { uid: officeId } ; - if(!query.where) query.where = { document: { folder: {office: officeWhereInput}}}; + const officeWhereInput: Prisma.OfficesWhereInput = { uid: officeId }; + if (!query.where) query.where = { document: { folder: { office: officeWhereInput } } }; query.where.document!.folder!.office = officeWhereInput; //call service to get prisma entity const fileEntities = await this.filesService.get(query); diff --git a/src/app/api/notary/FilesNotaryController.ts b/src/app/api/notary/FilesNotaryController.ts new file mode 100644 index 00000000..2fb116e2 --- /dev/null +++ b/src/app/api/notary/FilesNotaryController.ts @@ -0,0 +1,109 @@ +import authHandler from "@App/middlewares/AuthHandler"; +import FilesNotaryHandler from "@App/middlewares/OfficeMembershipHandlers/FilesNotaryHandler"; +import ApiController from "@Common/system/controller-pattern/ApiController"; +import { Controller, Get } from "@ControllerPattern/index"; +import { Prisma } from "@prisma/client"; +import FilesNotaryService from "@Services/common/FilesNotaryService/FilesService"; +import { Request, Response } from "express"; +import { File } from "le-coffre-resources/dist/Notary"; +import { Service } from "typedi"; + +@Controller() +@Service() +export default class FilesNotaryController extends ApiController { + constructor(private filesNotaryService: FilesNotaryService) { + super(); + } + + /** + * @description Get all Files + * @returns File[] list of Files + */ + @Get("/api/v1/notary/files-notary", [authHandler]) + protected async get(req: Request, response: Response) { + try { + //get query + let query: Prisma.FilesFindManyArgs = {}; + if (req.query["q"]) { + query = JSON.parse(req.query["q"] as string); + if (query.where?.uid) { + this.httpBadRequest(response, "You can't filter by uid"); + return; + } + } + const officeId: string = req.body.user.office_Id; + const officeWhereInput: Prisma.OfficesWhereInput = { uid: officeId }; + if (!query.where) query.where = { document: { folder: { office: officeWhereInput } } }; + query.where.document!.folder!.office = officeWhereInput; + //call service to get prisma entity + const fileEntities = await this.filesNotaryService.get(query); + + //Hydrate ressource with prisma entity + const files = File.hydrateArray(fileEntities, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response, files); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Get a specific File by uid + */ + @Get("/api/v1/notary/files-notary/download/:uid", [authHandler, FilesNotaryHandler]) + protected async download(req: Request, response: Response) { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "uid not found"); + return; + } + try { + const fileInfo = await this.filesNotaryService.download(uid); + + if (!fileInfo) { + this.httpNotFoundRequest(response, "file not found"); + return; + } + + response.setHeader("Content-Type", fileInfo.file.mimetype); + response.setHeader("Content-Disposition", `inline; filename=${encodeURIComponent(fileInfo.file.file_name)}`); + + this.httpSuccess(response, fileInfo.buffer); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + /** + * @description Get a specific File by uid + */ + @Get("/api/v1/notary/files-notary/:uid", [authHandler, FilesNotaryHandler]) + protected async getOneByUid(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } + + const fileEntity = await this.filesNotaryService.getByUid(uid); + + if (!fileEntity) { + this.httpNotFoundRequest(response, "file not found"); + return; + } + + //Hydrate ressource with prisma entity + const file = File.hydrate(fileEntity, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response, file); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } +} diff --git a/src/app/index.ts b/src/app/index.ts index 56b47310..f2bcaf1c 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -57,6 +57,7 @@ import NotesController from "./api/customer/NotesController"; import MailchimpController from "./api/notary/MailchimpController"; import DocumentsReminderController from "./api/notary/DocumentsReminderController"; import DocumentsNotaryController from "./api/notary/DocumentsNotaryController"; +import FilesNotaryController from "./api/notary/FilesNotaryController"; /** * @description This allow to declare all controllers used in the application @@ -120,7 +121,8 @@ export default { Container.get(RulesGroupsController); Container.get(NotesController); Container.get(MailchimpController); - Container.get(DocumentsReminderController) - Container.get(DocumentsNotaryController) + Container.get(DocumentsReminderController); + Container.get(DocumentsNotaryController); + Container.get(FilesNotaryController); }, }; diff --git a/src/app/middlewares/OfficeMembershipHandlers/FilesNotaryHandler.ts b/src/app/middlewares/OfficeMembershipHandlers/FilesNotaryHandler.ts new file mode 100644 index 00000000..b3453183 --- /dev/null +++ b/src/app/middlewares/OfficeMembershipHandlers/FilesNotaryHandler.ts @@ -0,0 +1,48 @@ +import HttpCodes from "@Common/system/controller-pattern/HttpCodes"; +import FilesNotaryService from "@Services/common/FilesNotaryService/FilesService"; +import DocumentsNotaryService from "@Services/notary/DocumentsNotaryService/DocumentsNotaryService"; +import { NextFunction, Request, Response } from "express"; +import Container from "typedi"; + +export default async function FilesNotaryHandler(req: Request, response: Response, next: NextFunction) { + try { + const officeId = req.body.user.office_Id; + let uid = req.path && req.path.split("/")[5]; + const document = req.body.document; + + if (document) { + const documentService = Container.get(DocumentsNotaryService); + const documentWithOffice = await documentService.getByUidWithOffice(document.uid!); + if (!documentWithOffice) { + response.status(HttpCodes.NOT_FOUND).send("Document not found"); + return; + } + if (documentWithOffice.folder.office?.uid != officeId) { + response.status(HttpCodes.UNAUTHORIZED).send("Unauthorized with this office"); + return; + } + } + + if (uid === "download") uid = req.path && req.path.split("/")[6]; + + if (uid) { + const fileService = Container.get(FilesNotaryService); + const file = await fileService.getByUidWithOffice(uid!); + + if (!file) { + response.status(HttpCodes.NOT_FOUND).send("File not found"); + return; + } + if (file.document_notary.folder.office.uid != officeId) { + response.status(HttpCodes.UNAUTHORIZED).send("Unauthorized with this office"); + return; + } + } + + next(); + } catch (error) { + console.error(error); + response.status(HttpCodes.INTERNAL_ERROR).send("Internal server error"); + return; + } +} diff --git a/src/services/common/FilesNotaryService/FilesService.ts b/src/services/common/FilesNotaryService/FilesService.ts new file mode 100644 index 00000000..7089a374 --- /dev/null +++ b/src/services/common/FilesNotaryService/FilesService.ts @@ -0,0 +1,90 @@ +import { BackendVariables } from "@Common/config/variables/Variables"; +import { Prisma } from "@prisma/client"; +import FilesNotaryRepository from "@Repositories/FilesNotaryRepository"; +import FilesRepository from "@Repositories/FilesRepository"; +import BaseService from "@Services/BaseService"; +import { FileNotary } from "le-coffre-resources/dist/Notary"; +import { Readable } from "stream"; +import { Service } from "typedi"; +import { v4 } from "uuid"; + +import CryptoService from "../CryptoService/CryptoService"; +import IpfsService from "../IpfsService/IpfsService"; + +@Service() +export default class FilesNotaryService extends BaseService { + constructor( + private filesRepository: FilesRepository, + private ipfsService: IpfsService, + private variables: BackendVariables, + private cryptoService: CryptoService, + private filesNotaryRepository: FilesNotaryRepository, + ) { + super(); + } + + /** + * @description : Get all files + * @throws {Error} If files cannot be ge + */ + public async get(query: Prisma.FilesFindManyArgs) { + return this.filesRepository.findMany(query); + } + + /** + * @description : Get a file by uid + * @throws {Error} If project cannot be created + */ + public async getByUid(uid: string) { + return this.filesNotaryRepository.findOneByUid(uid); + } + + /** + * @description : Get a file by uid with office + * @throws {Error} If project cannot be created + */ + public async getByUidWithOffice(uid: string) { + return this.filesNotaryRepository.findOneByUidWithOffice(uid); + } + + /** + * @description : Get a file by uid with document + * @throws {Error} If project cannot be created + */ + public async getByUidWithDocument(uid: string) { + return this.filesNotaryRepository.findOneByUidWithDocument(uid); + } + + /** + * @description : view a file + * @throws {Error} If file cannot be deleted + */ + public async download(uid: string) { + const file = await this.filesNotaryRepository.findOneByUid(uid); + if (!file?.uid) return null; + const fileResult = await fetch(file.file_path.concat("?pinataGatewayToken=").concat(this.variables.PINATA_GATEWAY_TOKEN)); + const fileArrayBuffer = await fileResult.arrayBuffer(); + return { file: file, buffer: await this.cryptoService.decrypt(Buffer.from(fileArrayBuffer), file.uid) }; + } + + /** + * @description : Create a new file + * @throws {Error} If file cannot be created + */ + public async create(file: FileNotary, fileData: Express.Multer.File) { + const key = v4(); + const encryptedFile = await this.cryptoService.encrypt(fileData.buffer, key); + const hash = await this.cryptoService.getHash(fileData.buffer); + + const upload = await this.ipfsService.pinFile(Readable.from(encryptedFile), fileData.originalname); + let fileToCreate: FileNotary = file; + fileToCreate.file_name = fileData.originalname; + fileToCreate.file_path = this.variables.PINATA_GATEWAY.concat(upload.IpfsHash); + fileToCreate.mimetype = fileData.mimetype; + fileToCreate.size = fileData.size; + fileToCreate.hash = hash; + fileToCreate.archived_at = null; + + return this.filesNotaryRepository.create(fileToCreate, key); + } +} diff --git a/src/services/common/FilesService/FilesService.ts b/src/services/common/FilesService/FilesService.ts index 0e41dc52..a05b2ae1 100644 --- a/src/services/common/FilesService/FilesService.ts +++ b/src/services/common/FilesService/FilesService.ts @@ -1,15 +1,14 @@ +import { BackendVariables } from "@Common/config/variables/Variables"; +import { Files, Prisma } from "@prisma/client"; import FilesRepository from "@Repositories/FilesRepository"; import BaseService from "@Services/BaseService"; -import { Service } from "typedi"; import { File } from "le-coffre-resources/dist/SuperAdmin"; +import { Readable } from "stream"; +import { Service } from "typedi"; +import { v4 } from "uuid"; + import CryptoService from "../CryptoService/CryptoService"; import IpfsService from "../IpfsService/IpfsService"; -import { BackendVariables } from "@Common/config/variables/Variables"; -import { Readable } from "stream"; -import { v4 } from "uuid"; -import { Files, Prisma } from "@prisma/client"; -import { FileNotary } from "le-coffre-resources/dist/Notary"; -import FilesNotaryRepository from "@Repositories/FilesNotaryRepository"; @Service() export default class FilesService extends BaseService { @@ -18,7 +17,6 @@ export default class FilesService extends BaseService { private ipfsService: IpfsService, private variables: BackendVariables, private cryptoService: CryptoService, - private filesNotaryRepository: FilesNotaryRepository ) { super(); } @@ -88,27 +86,6 @@ export default class FilesService extends BaseService { return this.filesRepository.create(fileToCreate, key); } - /** - * @description : Create a new file - * @throws {Error} If file cannot be created - */ - public async createFileNotary(file: FileNotary, fileData: Express.Multer.File) { - const key = v4(); - const encryptedFile = await this.cryptoService.encrypt(fileData.buffer, key); - const hash = await this.cryptoService.getHash(fileData.buffer); - - const upload = await this.ipfsService.pinFile(Readable.from(encryptedFile), fileData.originalname); - let fileToCreate: FileNotary = file; - fileToCreate.file_name = fileData.originalname; - fileToCreate.file_path = this.variables.PINATA_GATEWAY.concat(upload.IpfsHash); - fileToCreate.mimetype = fileData.mimetype; - fileToCreate.size = fileData.size; - fileToCreate.hash = hash; - fileToCreate.archived_at = null; - - return this.filesNotaryRepository.create(fileToCreate, key); - } - /** * @description : Modify a new file * @throws {Error} If file cannot be modified