From 3e64472821efc85266cef06f6b53b796e5b9391a Mon Sep 17 00:00:00 2001 From: Vins Date: Mon, 9 Sep 2024 05:45:17 +0200 Subject: [PATCH 1/9] Doing --- package.json | 2 +- src/app/api/notary/CustomersController.ts | 65 +++++- .../api/notary/DocumentsNotaryController.ts | 207 ++++++++++++++++++ .../api/notary/DocumentsReminderController.ts | 87 ++++++++ src/app/index.ts | 4 + .../migration.sql | 45 ++++ .../migration.sql | 16 ++ .../migration.sql | 17 ++ src/common/databases/schema.prisma | 45 ++++ src/common/emails/EmailBuilder.ts | 28 +++ src/common/emails/Templates/EmailTemplates.ts | 1 + .../repositories/DocumentsNotaryRepository.ts | 102 +++++++++ .../DocumentsReminderRepository.ts | 46 ++++ .../repositories/FilesNotaryRepository.ts | 83 +++++++ .../common/FilesService/FilesService.ts | 24 ++ .../common/IdNotService/IdNotService.ts | 64 +++--- .../CustomersService/CustomersService.ts | 21 +- .../DocumentsNotaryService.ts | 61 ++++++ .../DocumentsReminder/DocumentsReminder.ts | 28 +++ 19 files changed, 906 insertions(+), 40 deletions(-) create mode 100644 src/app/api/notary/DocumentsNotaryController.ts create mode 100644 src/app/api/notary/DocumentsReminderController.ts create mode 100644 src/common/databases/migrations/20240830075809_notary_documents/migration.sql create mode 100644 src/common/databases/migrations/20240906073736_email_reminder/migration.sql create mode 100644 src/common/databases/migrations/20240909032319_document_notary_customer/migration.sql create mode 100644 src/common/repositories/DocumentsNotaryRepository.ts create mode 100644 src/common/repositories/DocumentsReminderRepository.ts create mode 100644 src/common/repositories/FilesNotaryRepository.ts create mode 100644 src/services/notary/DocumentsNotaryService/DocumentsNotaryService.ts create mode 100644 src/services/notary/DocumentsReminder/DocumentsReminder.ts diff --git a/package.json b/package.json index ba8a4fdc..0483be4f 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "file-type-checker": "^1.0.8", "fp-ts": "^2.16.1", "jsonwebtoken": "^9.0.0", - "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.151", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.160", "module-alias": "^2.2.2", "monocle-ts": "^2.3.13", "multer": "^1.4.5-lts.1", diff --git a/src/app/api/notary/CustomersController.ts b/src/app/api/notary/CustomersController.ts index 83d3d1c9..2df0e275 100644 --- a/src/app/api/notary/CustomersController.ts +++ b/src/app/api/notary/CustomersController.ts @@ -7,13 +7,14 @@ import { Customer } from "le-coffre-resources/dist/Notary"; import { validateOrReject } from "class-validator"; import authHandler from "@App/middlewares/AuthHandler"; import ruleHandler from "@App/middlewares/RulesHandler"; -import { Prisma } from "@prisma/client"; +import { Documents, Prisma } from "@prisma/client"; import customerHandler from "@App/middlewares/OfficeMembershipHandlers/CustomerHandler"; +import DocumentsService from "@Services/notary/DocumentsService/DocumentsService"; @Controller() @Service() export default class CustomersController extends ApiController { - constructor(private customersService: CustomersService) { + constructor(private customersService: CustomersService, private documentsService: DocumentsService) { super(); } @@ -215,4 +216,64 @@ export default class CustomersController extends ApiController { return; } } + + /** + * @description Send a reminder to a specific customer by uid to signe specific documents + */ + @Post("/api/v1/notary/customers/:uid/send_reminder", [authHandler, ruleHandler, customerHandler]) + protected async sendReminder(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } + + const documentsUid = req.body.documentsUid; + if (!documentsUid || !Array.isArray(documentsUid)) { + this.httpBadRequest(response, "Invalid or missing documents"); + return; + } + + 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"); + return; + } + documentEntities.push(documentEntity); + } + + const customerEntity = await this.customersService.getByUid(uid, { contact: true, office : true }); + console.log("customerEntity", customerEntity); + + + if (!customerEntity) { + this.httpNotFoundRequest(response, "customer not found"); + return; + } + + //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 + this.httpSuccess(response, customer); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } } diff --git a/src/app/api/notary/DocumentsNotaryController.ts b/src/app/api/notary/DocumentsNotaryController.ts new file mode 100644 index 00000000..d6cf82e2 --- /dev/null +++ b/src/app/api/notary/DocumentsNotaryController.ts @@ -0,0 +1,207 @@ +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 { Document, DocumentNotary, FileNotary } from "le-coffre-resources/dist/Notary"; +import authHandler from "@App/middlewares/AuthHandler"; +import ruleHandler from "@App/middlewares/RulesHandler"; +import documentHandler from "@App/middlewares/OfficeMembershipHandlers/DocumentHandler"; +import OfficeFoldersService from "@Services/notary/OfficeFoldersService/OfficeFoldersService"; +import CustomersService from "@Services/admin/CustomersService/CustomersService"; +import UsersService from "@Services/notary/UsersService/UsersService"; +import FilesService from "@Services/common/FilesService/FilesService"; +// import NotificationBuilder from "@Common/notifications/NotificationBuilder"; + +@Controller() +@Service() +export default class DocumentsNotaryController extends ApiController { + constructor( + private documentsNotaryService: DocumentsNotaryService, + private officeFoldersService: OfficeFoldersService, + private customerService: CustomersService, + private userService: UsersService, + private filesService: FilesService, + ) { + super(); + } + + /** + * @description Get all documents + * @returns IDocument[] list of documents + */ + @Get("/api/v1/notary/documents_notary", [authHandler, ruleHandler]) + protected async get(req: Request, response: Response) { + try { + //get query + let query: Prisma.DocumentsNotaryFindManyArgs = {}; + 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 = { folder: { office: officeWhereInput } }; + query.where.folder!.office = officeWhereInput; + + //call service to get prisma entity + const documentEntities = await this.documentsNotaryService.get(query); + + //Hydrate ressource with prisma entity + const documents = Document.hydrateArray(documentEntities, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response, documents); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + /** + * @description Create a new document + * @returns IDocument created + */ + @Post("/api/v1/notary/documents_notary", [authHandler]) + protected async post(req: Request, response: Response) { + try { + 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; + + const documentNotaryEntity = DocumentNotary.hydrate({ + 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); + console.log("fileEntityCreated", fileEntityCreated); + + + + // //init Document resource with request body values + // const documentNotaryEntity = DocumentNotary.hydrate(req.body); + // console.log(documentNotaryEntity); + + // const folder = await this.officeFoldersService.getByUid(documentNotaryEntity.folder?.uid!, { + // folder_anchor: true, + // }); + // if (!folder) { + // this.httpBadRequest(response, "Folder not found"); + // return; + // } + + // const folderRessource = OfficeFolder.hydrate(folder); + // if (folderRessource.folder_anchor) { + // this.httpBadRequest(response, "Cannot add document on an anchored or anchoring folder"); + // return; + // } + + // //validate document + // await validateOrReject(documentNotaryEntity, { groups: ["createDocument"], forbidUnknownValues: false }); + + // //call service to get prisma entity + // const documentNotaryEntityCreated = await this.documentsNotaryService.create(documentNotaryEntity); + + // //Hydrate ressource with prisma entity + // const documentNotary = DocumentNotary.hydrate(documentNotaryEntityCreated, { + // strategy: "excludeAll", + // }); + + // //success + // this.httpCreated(response, documentNotary); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + /** + * @description Delete a specific document + */ + @Delete("/api/v1/notary/documents_notary/:uid", [authHandler, ruleHandler, documentHandler]) + protected async delete(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } + + const documentFound = await this.documentsNotaryService.getByUid(uid); + + if (!documentFound) { + this.httpNotFoundRequest(response, "document not found"); + return; + } + + //call service to get prisma entity + const documentEntity: DocumentsNotary = await this.documentsNotaryService.delete(uid); + + //Hydrate ressource with prisma entity + const document = Document.hydrate(documentEntity, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response, document); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + /** + * @description Get a specific document by uid + */ + @Get("/api/v1/notary/documents_notary/:uid", [authHandler, ruleHandler, documentHandler]) + protected async getOneByUid(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } + //get query + let query; + if (req.query["q"]) { + query = JSON.parse(req.query["q"] as string); + } + + const documentEntity = await this.documentsNotaryService.getByUid(uid, query); + + if (!documentEntity) { + this.httpNotFoundRequest(response, "document not found"); + return; + } + + //Hydrate ressource with prisma entity + const document = Document.hydrate(documentEntity, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response, document); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } +} diff --git a/src/app/api/notary/DocumentsReminderController.ts b/src/app/api/notary/DocumentsReminderController.ts new file mode 100644 index 00000000..27829d6d --- /dev/null +++ b/src/app/api/notary/DocumentsReminderController.ts @@ -0,0 +1,87 @@ +import { Response, Request } from "express"; +import { Controller, Get } from "@ControllerPattern/index"; +import ApiController from "@Common/system/controller-pattern/ApiController"; +import { Service } from "typedi"; +import { Prisma } from "@prisma/client"; +import { DocumentReminder } from "le-coffre-resources/dist/Notary"; + +import authHandler from "@App/middlewares/AuthHandler"; + +import DocumentsReminderService from "@Services/notary/DocumentsReminder/DocumentsReminder"; +// import NotificationBuilder from "@Common/notifications/NotificationBuilder"; + +@Controller() +@Service() +export default class DocumentsReminderController extends ApiController { + constructor( + private documentsReminderService: DocumentsReminderService, + ) { + super(); + } + + /** + * @description Get all documents + * @returns IDocument[] list of documents + */ + @Get("/api/v1/notary/document_reminders", [authHandler]) + protected async get(req: Request, response: Response) { + try { + //get query + let query: Prisma.DocumentsReminderFindManyArgs = {}; + 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; + } + } + + //call service to get prisma entity + const documentReminderEntities = await this.documentsReminderService.get(query); + + //Hydrate ressource with prisma entity + const documentReminders = DocumentReminder.hydrateArray(documentReminderEntities, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response, documentReminders); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + // /** + // * @description Get a specific document by uid + // */ + // @Get("/api/v1/notary/document_reminders/:uid", [authHandler, ruleHandler, documentHandler]) + // protected async getOneByUid(req: Request, response: Response) { + // try { + // const uid = req.params["uid"]; + // if (!uid) { + // this.httpBadRequest(response, "No uid provided"); + // return; + // } + // //get query + // let query; + // if (req.query["q"]) { + // query = JSON.parse(req.query["q"] as string); + // } + + // const documentEntity = await this.documentsNotaryService.getByUid(uid, query); + + // if (!documentEntity) { + // this.httpNotFoundRequest(response, "document not found"); + // return; + // } + + // //Hydrate ressource with prisma entity + // const document = Document.hydrate(documentEntity, { strategy: "excludeAll" }); + + // //success + // this.httpSuccess(response, document); + // } catch (error) { + // this.httpInternalError(response, error); + // return; + // } + // } +} diff --git a/src/app/index.ts b/src/app/index.ts index 3b747d8a..56b47310 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -55,6 +55,8 @@ import StripeWebhooks from "@Common/webhooks/stripeWebhooks"; import RulesGroupsController from "./api/admin/RulesGroupsController"; import NotesController from "./api/customer/NotesController"; import MailchimpController from "./api/notary/MailchimpController"; +import DocumentsReminderController from "./api/notary/DocumentsReminderController"; +import DocumentsNotaryController from "./api/notary/DocumentsNotaryController"; /** * @description This allow to declare all controllers used in the application @@ -118,5 +120,7 @@ export default { Container.get(RulesGroupsController); Container.get(NotesController); Container.get(MailchimpController); + Container.get(DocumentsReminderController) + Container.get(DocumentsNotaryController) }, }; diff --git a/src/common/databases/migrations/20240830075809_notary_documents/migration.sql b/src/common/databases/migrations/20240830075809_notary_documents/migration.sql new file mode 100644 index 00000000..c0136620 --- /dev/null +++ b/src/common/databases/migrations/20240830075809_notary_documents/migration.sql @@ -0,0 +1,45 @@ +-- CreateTable +CREATE TABLE "documents_notary" ( + "uid" TEXT NOT NULL, + "folder_uid" VARCHAR(255) NOT NULL, + "depositor_uid" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "documents_notary_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "files_notary" ( + "uid" TEXT NOT NULL, + "document_uid" VARCHAR(255) NOT NULL, + "file_path" VARCHAR(255) NOT NULL, + "file_name" VARCHAR(255) NOT NULL, + "mimetype" VARCHAR(255) NOT NULL, + "hash" VARCHAR(255) NOT NULL, + "size" INTEGER NOT NULL, + "archived_at" TIMESTAMP(3), + "key" VARCHAR(255), + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "files_notary_pkey" PRIMARY KEY ("uid") +); + +-- CreateIndex +CREATE UNIQUE INDEX "documents_notary_uid_key" ON "documents_notary"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "files_notary_uid_key" ON "files_notary"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "files_notary_file_path_key" ON "files_notary"("file_path"); + +-- AddForeignKey +ALTER TABLE "documents_notary" ADD CONSTRAINT "documents_notary_folder_uid_fkey" FOREIGN KEY ("folder_uid") REFERENCES "office_folders"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "documents_notary" ADD CONSTRAINT "documents_notary_depositor_uid_fkey" FOREIGN KEY ("depositor_uid") REFERENCES "users"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "files_notary" ADD CONSTRAINT "files_notary_document_uid_fkey" FOREIGN KEY ("document_uid") REFERENCES "documents_notary"("uid") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/common/databases/migrations/20240906073736_email_reminder/migration.sql b/src/common/databases/migrations/20240906073736_email_reminder/migration.sql new file mode 100644 index 00000000..04034ca4 --- /dev/null +++ b/src/common/databases/migrations/20240906073736_email_reminder/migration.sql @@ -0,0 +1,16 @@ +-- CreateTable +CREATE TABLE "documents_reminder" ( + "uid" TEXT NOT NULL, + "document_uid" VARCHAR(255) NOT NULL, + "reminder_date" TIMESTAMP(3) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "documents_reminder_pkey" PRIMARY KEY ("uid") +); + +-- CreateIndex +CREATE UNIQUE INDEX "documents_reminder_uid_key" ON "documents_reminder"("uid"); + +-- AddForeignKey +ALTER TABLE "documents_reminder" ADD CONSTRAINT "documents_reminder_document_uid_fkey" FOREIGN KEY ("document_uid") REFERENCES "documents"("uid") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/common/databases/migrations/20240909032319_document_notary_customer/migration.sql b/src/common/databases/migrations/20240909032319_document_notary_customer/migration.sql new file mode 100644 index 00000000..9b636a65 --- /dev/null +++ b/src/common/databases/migrations/20240909032319_document_notary_customer/migration.sql @@ -0,0 +1,17 @@ +/* + Warnings: + + - You are about to drop the column `archived_at` on the `files_notary` table. All the data in the column will be lost. + - You are about to drop the column `key` on the `files_notary` table. All the data in the column will be lost. + - Added the required column `customer_uid` to the `documents_notary` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "documents_notary" ADD COLUMN "customer_uid" VARCHAR(255) NOT NULL; + +-- AlterTable +ALTER TABLE "files_notary" DROP COLUMN "archived_at", +DROP COLUMN "key"; + +-- AddForeignKey +ALTER TABLE "documents_notary" ADD CONSTRAINT "documents_notary_customer_uid_fkey" FOREIGN KEY ("customer_uid") REFERENCES "customers"("uid") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index 259c59f1..a88f4839 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -69,6 +69,7 @@ model Users { votes Votes[] user_notifications UserNotifications[] seats Seats[] + documents_notary DocumentsNotary[] @@map("users") } @@ -129,6 +130,7 @@ model Customers { office Offices @relation(fields: [office_uid], references: [uid], onDelete: Cascade) office_uid String @db.VarChar(255) notes Notes[] + documents_notary DocumentsNotary[] @@map("customers") } @@ -170,6 +172,7 @@ model OfficeFolders { stakeholders Users[] @relation("OfficeFolderHasStakeholders") customers Customers[] @relation("OfficeFolderHasCustomers") documents Documents[] + documents_notary DocumentsNotary[] folder_anchor OfficeFolderAnchors? @relation(fields: [folder_anchor_uid], references: [uid]) folder_anchor_uid String? @unique @db.VarChar(255) @@ -215,10 +218,37 @@ model Documents { updated_at DateTime? @updatedAt files Files[] document_history DocumentHistory[] + reminders DocumentsReminder[] @@map("documents") } +model DocumentsNotary { + uid String @id @unique @default(uuid()) + folder OfficeFolders @relation(fields: [folder_uid], references: [uid]) + folder_uid String @db.VarChar(255) + depositor Users @relation(fields: [depositor_uid], references: [uid], onDelete: Cascade) + depositor_uid String @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + files FilesNotary[] + customer Customers @relation(fields: [customer_uid], references: [uid], onDelete: Cascade) + customer_uid String @db.VarChar(255) + + @@map("documents_notary") +} + +model DocumentsReminder { + uid String @id @unique @default(uuid()) + document Documents @relation(fields: [document_uid], references: [uid], onDelete: Cascade) + document_uid String @db.VarChar(255) + reminder_date DateTime + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + + @@map("documents_reminder") +} + model DocumentHistory { uid String @id @unique @default(uuid()) document_status EDocumentStatus @default(ASKED) @@ -248,6 +278,21 @@ model Files { @@map("files") } +model FilesNotary { + uid String @id @unique @default(uuid()) + document_notary DocumentsNotary @relation(fields: [document_uid], references: [uid], onDelete: Cascade) + document_uid String @db.VarChar(255) + file_path String @unique @db.VarChar(255) + file_name String @db.VarChar(255) + mimetype String @db.VarChar(255) + hash String @db.VarChar(255) + size Int + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + + @@map("files_notary") +} + model DocumentTypes { uid String @id @unique @default(uuid()) name String @db.VarChar(255) diff --git a/src/common/emails/EmailBuilder.ts b/src/common/emails/EmailBuilder.ts index f3296d13..7d795ba9 100644 --- a/src/common/emails/EmailBuilder.ts +++ b/src/common/emails/EmailBuilder.ts @@ -7,6 +7,7 @@ import MailchimpService from "@Services/common/MailchimpService/MailchimpService import { BackendVariables } from "@Common/config/variables/Variables"; import UsersService from "@Services/super-admin/UsersService/UsersService"; import User from "le-coffre-resources/dist/SuperAdmin"; +import { Customer } from "le-coffre-resources/dist/Notary"; @Service() export default class EmailBuilder { @@ -138,4 +139,31 @@ export default class EmailBuilder { } + + public async sendReminder(customer: Customer, documents: Documents[]) { + const to = customer.contact!.email; + const templateVariables = { + office_name: customer.office?.name, + last_name: customer.contact!.last_name, + first_name: customer.contact!.first_name, + link: this.variables.APP_HOST, + }; + + const templateName = ETemplates.DOCUMENT_REMINDER; + const subject = "Vous avez des documents à déposer pour votre dossier."; + + this.mailchimpService.create({ + templateName, + to, + subject, + templateVariables, + uid: "", + from: null, + cc: [], + cci: [], + sentAt: null, + nbTrySend: null, + lastTrySendDate: null, + }); + } } diff --git a/src/common/emails/Templates/EmailTemplates.ts b/src/common/emails/Templates/EmailTemplates.ts index 520098e2..4943b885 100644 --- a/src/common/emails/Templates/EmailTemplates.ts +++ b/src/common/emails/Templates/EmailTemplates.ts @@ -3,4 +3,5 @@ export const ETemplates = { DOCUMENT_REFUSED: "DOCUMENT_REFUSED", DOCUMENT_RECAP: "DOCUMENT_RECAP", SUBSCRIPTION_INVITATION: "SUBSCRIPTION_INVITATION", + DOCUMENT_REMINDER: "DOCUMENT_REMINDER", }; \ No newline at end of file diff --git a/src/common/repositories/DocumentsNotaryRepository.ts b/src/common/repositories/DocumentsNotaryRepository.ts new file mode 100644 index 00000000..4d785adb --- /dev/null +++ b/src/common/repositories/DocumentsNotaryRepository.ts @@ -0,0 +1,102 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { DocumentsNotary, Prisma } from "@prisma/client"; +import { DocumentNotary } from "le-coffre-resources/dist/Notary"; + +@Service() +export default class DocumentsNotaryRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().documentsNotary; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many documents + */ + public async findMany(query: Prisma.DocumentsNotaryFindManyArgs) { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Create a document + */ + public async create(document: DocumentNotary): Promise { + const createArgs: Prisma.DocumentsNotaryCreateArgs = { + data: { + folder: { + connect: { + uid: document.folder!.uid, + }, + }, + depositor: { + connect: { + uid: document.depositor!.uid, + }, + }, + customer: { + connect: { + uid: document.customer!.uid, + }, + }, + }, + }; + + const documentCreated = await this.model.create({ ...createArgs }); + + return documentCreated; + } + + /** + * @description : Delete a document + */ + public async delete(uid: string): Promise { + return this.model.delete({ + where: { + uid: uid, + }, + }); + } + + /** + * @description : Find unique document + */ + public async findOneByUid(uid: string, query?: Prisma.DocumentsNotaryInclude): Promise { + return this.model.findUnique({ + where: { + uid: uid, + }, + include: query, + }); + } + + /** + * @description : Find unique document with relations + */ + public async findOneByUidWithOffice(uid: string) { + return this.model.findUnique({ + where: { + uid: uid, + }, + include: { folder: { include: { office: true } } }, + }); + } + + /** + * @description : Find unique document with relations + */ + public async findOneByUidWithFiles(uid: string) { + return this.model.findUnique({ + where: { + uid: uid, + }, + include: { files: true }, + }); + } +} diff --git a/src/common/repositories/DocumentsReminderRepository.ts b/src/common/repositories/DocumentsReminderRepository.ts new file mode 100644 index 00000000..1b5b1ac2 --- /dev/null +++ b/src/common/repositories/DocumentsReminderRepository.ts @@ -0,0 +1,46 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { DocumentsReminder, Prisma } from "@prisma/client"; +import { DocumentReminder } from "le-coffre-resources/dist/Notary"; + +@Service() +export default class DocumentsReminderRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().documentsReminder; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many documents + */ + public async findMany(query: Prisma.DocumentsReminderFindManyArgs) { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Create a document + */ + public async create(documentReminder: DocumentReminder): Promise { + const createArgs: Prisma.DocumentsReminderCreateArgs = { + data: { + document: { + connect: { + uid: documentReminder.document!.uid, + }, + }, + reminder_date: new Date(), + }, + }; + + const documentReminderCreated = await this.model.create({ ...createArgs }); + + return documentReminderCreated; + } +} diff --git a/src/common/repositories/FilesNotaryRepository.ts b/src/common/repositories/FilesNotaryRepository.ts new file mode 100644 index 00000000..6f05e843 --- /dev/null +++ b/src/common/repositories/FilesNotaryRepository.ts @@ -0,0 +1,83 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { FilesNotary, Prisma } from "@prisma/client"; +import { FileNotary } from "le-coffre-resources/dist/Notary"; + +@Service() +export default class FilesNotaryRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().filesNotary; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many files + */ + public async findMany(query: Prisma.FilesNotaryFindManyArgs) { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Create a file linked to a document + */ + public async create(file: FileNotary, key: string): Promise { + const createArgs: Prisma.FilesNotaryCreateArgs = { + data: { + document_notary: { + connect: { + uid: file.document!.uid, + }, + }, + file_name: file.file_name, + file_path: file.file_path, + mimetype: file.mimetype, + hash: file.hash, + size: file.size, + }, + }; + return this.model.create({ ...createArgs, include: { document_notary: true } }); + } + + /** + * @description : Find unique file + */ + public async findOneByUid(uid: string, query?: Prisma.FilesNotaryInclude) { + return this.model.findUnique({ + where: { + uid: uid, + }, + include: query, + }); + } + + /** + * @description : Find unique file with office + */ + public async findOneByUidWithOffice(uid: string) { + return this.model.findUnique({ + where: { + uid: uid, + }, + include: { document_notary: { include: { folder: { include: { office: true } } } } }, + }); + } + + /** + * @description : Find unique file with document + */ + public async findOneByUidWithDocument(uid: string) { + return this.model.findUnique({ + where: { + uid: uid, + }, + include: { document_notary: true }, + }); + } +} diff --git a/src/services/common/FilesService/FilesService.ts b/src/services/common/FilesService/FilesService.ts index ef02f19b..0e41dc52 100644 --- a/src/services/common/FilesService/FilesService.ts +++ b/src/services/common/FilesService/FilesService.ts @@ -8,6 +8,8 @@ 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 { @@ -16,6 +18,7 @@ export default class FilesService extends BaseService { private ipfsService: IpfsService, private variables: BackendVariables, private cryptoService: CryptoService, + private filesNotaryRepository: FilesNotaryRepository ) { super(); } @@ -85,6 +88,27 @@ 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 diff --git a/src/services/common/IdNotService/IdNotService.ts b/src/services/common/IdNotService/IdNotService.ts index f18721ad..e9a8cda1 100644 --- a/src/services/common/IdNotService/IdNotService.ts +++ b/src/services/common/IdNotService/IdNotService.ts @@ -113,24 +113,22 @@ export default class IdNotService extends BaseService { super(); } - public async getIdNotToken(code: string) { + public async getIdNotToken(code: string) { const query = new URLSearchParams({ client_id: this.variables.IDNOT_CLIENT_ID, client_secret: this.variables.IDNOT_CLIENT_SECRET, redirect_uri: this.variables.IDNOT_REDIRECT_URL, code: code, grant_type: "authorization_code", - }); - + }); + const token = await fetch(this.variables.IDNOT_BASE_URL + this.variables.IDNOT_CONNEXION_URL + "?" + query, { method: "POST" }); - - if(token.status !== 200) console.error(await token.text()); - + + if (token.status !== 200) console.error(await token.text()); + const decodedToken = (await token.json()) as IIdNotToken; - - - const decodedIdToken = jwt.decode(decodedToken.id_token) as IdNotJwtPayload; - + + const decodedIdToken = jwt.decode(decodedToken.id_token) as IdNotJwtPayload; return decodedIdToken; } @@ -178,7 +176,7 @@ export default class IdNotService extends BaseService { } public async getOfficeMemberships(officeId: string) { - const officeInfos = await this.officeService.getByUid(officeId); + const officeInfos = await this.officeService.getByUid(officeId); const office = Office.hydrate(officeInfos!); const searchParams = new URLSearchParams({ key: this.variables.IDNOT_API_KEY, @@ -187,7 +185,7 @@ export default class IdNotService extends BaseService { await fetch(`${this.variables.IDNOT_API_BASE_URL}/api/pp/v2/entites/${office.idNot}/personnes?` + searchParams, { method: "GET", }) - ).json()) as any; + ).json()) as any; } public getOfficeStatus(statusName: string) { @@ -291,13 +289,14 @@ export default class IdNotService extends BaseService { await this.userService.updateCheckedAt(user.uid!); } - public async updateOffice(officeId: string) { + public async updateOffice(officeId: string) { const officeInfos = await this.officeService.getByUid(officeId); const office = Office.hydrate(officeInfos!); const searchParams = new URLSearchParams({ key: this.variables.IDNOT_API_KEY, }); - const officeRawData = await fetch(`${this.variables.IDNOT_API_BASE_URL}/api/pp/v2/entities/${office.idNot}?` + searchParams, { + + const officeRawData = await fetch(`${this.variables.IDNOT_API_BASE_URL}/api/pp/v2/entites/${office.idNot}?` + searchParams, { method: "GET", }); if (officeRawData.status === 404) { @@ -339,7 +338,7 @@ export default class IdNotService extends BaseService { const officeLocationData = (await ( await fetch(`${this.variables.IDNOT_API_BASE_URL + userData.entite.locationsUrl}?` + searchParams, { method: "GET" }) ).json()) as IOfficeLocation; - + const office = await this.officeService.get({ where: { idNot: decodedToken.entity_idn } }); // if(officeLocationData.result[0]!.adrGeoCodePostal.slice(0,2) !== "35") { @@ -378,17 +377,17 @@ export default class IdNotService extends BaseService { }, }; - if(!userToAdd.contact.email) { + if (!userToAdd.contact.email) { return null; } - + let userHydrated = User.hydrate(userToAdd); const user = await this.userService.create(userHydrated); const userOffice = await this.officeService.getByUid(user.office_uid); userHydrated = User.hydrate(user); const userOfficeHydrated = Office.hydrate(userOffice!); - if(office.length === 0) { + if (office.length === 0) { const officeRoles = await this.officeRolesService.get({ where: { office: { idNot: "0000" } }, include: { office: true, rules: true }, @@ -401,17 +400,16 @@ export default class IdNotService extends BaseService { where: { office: { idNot: "0000" } }, include: { office: true }, }); - + const officeRolesHydrated = OfficeRole.hydrateArray(officeRoles); const deedTypesHydrated = DeedType.hydrateArray(deedTypes); const documentTypesHydrated = DocumentType.hydrateArray(documentTypes); - - + await this.duplicateOfficeRoles(officeRolesHydrated, userOfficeHydrated); const documentTypesCreated = await this.duplicateDocumentTypes(documentTypesHydrated, userOfficeHydrated); await this.duplicateDeedTypes(deedTypesHydrated, documentTypesCreated, userOfficeHydrated); } - + const officeRole = await this.getOfficeRole(userData.typeLien.name, user.office_uid); if (officeRole) { @@ -427,37 +425,35 @@ export default class IdNotService extends BaseService { public async duplicateDocumentTypes(documentTypes: DocumentType[], office: Office): Promise { let newDocumentTypes: DocumentType[] = []; - for(const documentType of documentTypes) { + for (const documentType of documentTypes) { documentType.office = office; const documentTypeCreated = await this.documentTypesService.create(documentType); newDocumentTypes.push(DocumentType.hydrate(documentTypeCreated)); - }; + } return newDocumentTypes; } public async duplicateDeedTypes(deedTypes: DeedType[], documentTypes: DocumentType[], office: Office) { - for (const deedType of deedTypes) { + for (const deedType of deedTypes) { let newDocumentTypes: DocumentType[] = []; - for (const document of deedType.document_types!) { + for (const document of deedType.document_types!) { const newDocumentType = documentTypes.find((documentType) => documentType.name === document.name); - if(!newDocumentType) continue; + if (!newDocumentType) continue; newDocumentTypes.push(newDocumentType!); - }; + } deedType.document_types = newDocumentTypes; deedType.office = office; await this.deedTypesService.create(deedType); - }; + } } - public async duplicateOfficeRoles(officeRoles: OfficeRole[], office: Office){ - for(const officeRole of officeRoles) { + public async duplicateOfficeRoles(officeRoles: OfficeRole[], office: Office) { + for (const officeRole of officeRoles) { officeRole.office = office; await this.officeRolesService.create(officeRole); - }; + } } - - public async updateUsers() { const usersReq = await this.userService.getUsersToBeChecked(); const users = User.hydrateArray(usersReq); diff --git a/src/services/notary/CustomersService/CustomersService.ts b/src/services/notary/CustomersService/CustomersService.ts index 72e6923d..880d7c8d 100644 --- a/src/services/notary/CustomersService/CustomersService.ts +++ b/src/services/notary/CustomersService/CustomersService.ts @@ -1,12 +1,14 @@ -import { Customers, Prisma } from "@prisma/client"; +import EmailBuilder from "@Common/emails/EmailBuilder"; +import { Customers, Documents, Prisma } from "@prisma/client"; import CustomersRepository from "@Repositories/CustomersRepository"; import BaseService from "@Services/BaseService"; -import { Customer } from "le-coffre-resources/dist/Notary"; +import { Customer, DocumentReminder } from "le-coffre-resources/dist/Notary"; import { Service } from "typedi"; +import DocumentsReminderService from "../DocumentsReminder/DocumentsReminder"; @Service() export default class CustomersService extends BaseService { - constructor(private customerRepository: CustomersRepository) { + constructor(private customerRepository: CustomersRepository, private emailBuilder: EmailBuilder, private documentsReminderService : DocumentsReminderService) { super(); } @@ -49,4 +51,17 @@ export default class CustomersService extends BaseService { public async getByContact(contactUid: string): Promise { return this.customerRepository.findOneByContact(contactUid); } + + public async sendDocumentsReminder(customer: Customer, documents: Documents[]): Promise { + //Call email builder to send mail + const email = this.emailBuilder.sendReminder(customer, documents); + //Call DocumentsReminder service to create add the reminder in database + if (!email) return; + for (const document of documents) { + //Create document reminder + const documentReminder = new DocumentReminder(); + documentReminder.document = document; + await this.documentsReminderService.create(documentReminder); + } + } } diff --git a/src/services/notary/DocumentsNotaryService/DocumentsNotaryService.ts b/src/services/notary/DocumentsNotaryService/DocumentsNotaryService.ts new file mode 100644 index 00000000..865752ca --- /dev/null +++ b/src/services/notary/DocumentsNotaryService/DocumentsNotaryService.ts @@ -0,0 +1,61 @@ +import { DocumentsNotary, Prisma } from "@prisma/client"; +import { Document, DocumentNotary } from "le-coffre-resources/dist/Notary"; +import DocumentsNotaryRepository from "@Repositories/DocumentsNotaryRepository"; +import BaseService from "@Services/BaseService"; +import { Service } from "typedi"; + +@Service() +export default class DocumentsService extends BaseService { + constructor(private documentsNotaryRepository: DocumentsNotaryRepository) { + super(); + } + + /** + * @description : Get all documents + * @throws {Error} If documents cannot be get + */ + public async get(query: Prisma.DocumentsNotaryFindManyArgs) { + return this.documentsNotaryRepository.findMany(query); + } + + /** + * @description : Create a new document + * @throws {Error} If document cannot be created + */ + public async create(document: DocumentNotary): Promise { + return this.documentsNotaryRepository.create(document); + } + + /** + * @description : Delete a document + * @throws {Error} If document cannot be deleted + */ + public async delete(uid: string): Promise { + const documentEntity = await this.documentsNotaryRepository.findOneByUid(uid, { files: true }); + if (!documentEntity) throw new Error("document not found"); + const document = Document.hydrate(documentEntity, { strategy: "excludeAll" }); + + const isDocumentEmpty = document.files && !document!.files.find((file) => file.archived_at === null); + + if (!isDocumentEmpty && document.document_status !== "REFUSED") { + throw new Error("Can't delete a document with file"); + } + return this.documentsNotaryRepository.delete(uid); + } + + /** + * @description : Get a document by uid + * @throws {Error} If document cannot be get by uid + */ + public async getByUid(uid: string, query?: Prisma.DocumentsNotaryInclude): Promise { + return this.documentsNotaryRepository.findOneByUid(uid, query); + } + + /** + * @description : Get a document by uid + * @throws {Error} If document cannot be get by uid + */ + public async getByUidWithOffice(uid: string) { + return this.documentsNotaryRepository.findOneByUidWithOffice(uid); + } +} diff --git a/src/services/notary/DocumentsReminder/DocumentsReminder.ts b/src/services/notary/DocumentsReminder/DocumentsReminder.ts new file mode 100644 index 00000000..3ee1f823 --- /dev/null +++ b/src/services/notary/DocumentsReminder/DocumentsReminder.ts @@ -0,0 +1,28 @@ +import { DocumentsReminder, Prisma } from "@prisma/client"; +import { DocumentReminder } from "le-coffre-resources/dist/Notary"; +import BaseService from "@Services/BaseService"; +import { Service } from "typedi"; +import DocumentsReminderRepository from "@Repositories/DocumentsReminderRepository"; + +@Service() +export default class DocumentsReminderService extends BaseService { + constructor(private documentsReminderRepository: DocumentsReminderRepository) { + super(); + } + + /** + * @description : Get all documents + * @throws {Error} If documents cannot be get + */ + public async get(query: Prisma.DocumentsReminderFindManyArgs) { + return this.documentsReminderRepository.findMany(query); + } + + /** + * @description : Create a new document + * @throws {Error} If document cannot be created + */ + public async create(document: DocumentReminder): Promise { + return this.documentsReminderRepository.create(document); + } +} From 8f5974ab59f74a98ce95a34a89f49d549aaaa673 Mon Sep 17 00:00:00 2001 From: Vins Date: Mon, 9 Sep 2024 11:15:28 +0200 Subject: [PATCH 2/9] Fixed Notary Documents --- .../api/notary/DocumentsNotaryController.ts | 130 +++++++++--------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/src/app/api/notary/DocumentsNotaryController.ts b/src/app/api/notary/DocumentsNotaryController.ts index d6cf82e2..1552646e 100644 --- a/src/app/api/notary/DocumentsNotaryController.ts +++ b/src/app/api/notary/DocumentsNotaryController.ts @@ -1,13 +1,11 @@ import { Response, Request } from "express"; -import { Controller, Delete, Get, Post } from "@ControllerPattern/index"; +import { Controller, 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 { Document, DocumentNotary, FileNotary } from "le-coffre-resources/dist/Notary"; +import { Prisma } from "@prisma/client"; +import { DocumentNotary, FileNotary } from "le-coffre-resources/dist/Notary"; import authHandler from "@App/middlewares/AuthHandler"; -import ruleHandler from "@App/middlewares/RulesHandler"; -import documentHandler from "@App/middlewares/OfficeMembershipHandlers/DocumentHandler"; import OfficeFoldersService from "@Services/notary/OfficeFoldersService/OfficeFoldersService"; import CustomersService from "@Services/admin/CustomersService/CustomersService"; import UsersService from "@Services/notary/UsersService/UsersService"; @@ -31,7 +29,7 @@ export default class DocumentsNotaryController extends ApiController { * @description Get all documents * @returns IDocument[] list of documents */ - @Get("/api/v1/notary/documents_notary", [authHandler, ruleHandler]) + @Get("/api/v1/notary/documents_notary", [authHandler]) protected async get(req: Request, response: Response) { try { //get query @@ -52,7 +50,7 @@ export default class DocumentsNotaryController extends ApiController { const documentEntities = await this.documentsNotaryService.get(query); //Hydrate ressource with prisma entity - const documents = Document.hydrateArray(documentEntities, { strategy: "excludeAll" }); + const documents = DocumentNotary.hydrateArray(documentEntities, { strategy: "excludeAll" }); //success this.httpSuccess(response, documents); @@ -69,6 +67,8 @@ 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; const customer = await this.customerService.getByUid(req.body.customerUid); @@ -95,7 +95,7 @@ export default class DocumentsNotaryController extends ApiController { const fileEntity = FileNotary.hydrate(JSON.parse(query)); - const fileEntityCreated = await this.filesService.createFileNotary(fileEntity, req.file); + const fileEntityCreated = await this.filesService.createFileNotary(fileEntity, req.file!); console.log("fileEntityCreated", fileEntityCreated); @@ -137,71 +137,71 @@ export default class DocumentsNotaryController extends ApiController { } } - /** - * @description Delete a specific document - */ - @Delete("/api/v1/notary/documents_notary/:uid", [authHandler, ruleHandler, documentHandler]) - protected async delete(req: Request, response: Response) { - try { - const uid = req.params["uid"]; - if (!uid) { - this.httpBadRequest(response, "No uid provided"); - return; - } + // /** + // * @description Delete a specific document + // */ + // @Delete("/api/v1/notary/documents_notary/:uid", [authHandler, ruleHandler, documentHandler]) + // protected async delete(req: Request, response: Response) { + // try { + // const uid = req.params["uid"]; + // if (!uid) { + // this.httpBadRequest(response, "No uid provided"); + // return; + // } - const documentFound = await this.documentsNotaryService.getByUid(uid); + // const documentFound = await this.documentsNotaryService.getByUid(uid); - if (!documentFound) { - this.httpNotFoundRequest(response, "document not found"); - return; - } + // if (!documentFound) { + // this.httpNotFoundRequest(response, "document not found"); + // return; + // } - //call service to get prisma entity - const documentEntity: DocumentsNotary = await this.documentsNotaryService.delete(uid); + // //call service to get prisma entity + // const documentEntity: DocumentsNotary = await this.documentsNotaryService.delete(uid); - //Hydrate ressource with prisma entity - const document = Document.hydrate(documentEntity, { strategy: "excludeAll" }); + // //Hydrate ressource with prisma entity + // const document = Document.hydrate(documentEntity, { strategy: "excludeAll" }); - //success - this.httpSuccess(response, document); - } catch (error) { - this.httpInternalError(response, error); - return; - } - } + // //success + // this.httpSuccess(response, document); + // } catch (error) { + // this.httpInternalError(response, error); + // return; + // } + // } - /** - * @description Get a specific document by uid - */ - @Get("/api/v1/notary/documents_notary/:uid", [authHandler, ruleHandler, documentHandler]) - protected async getOneByUid(req: Request, response: Response) { - try { - const uid = req.params["uid"]; - if (!uid) { - this.httpBadRequest(response, "No uid provided"); - return; - } - //get query - let query; - if (req.query["q"]) { - query = JSON.parse(req.query["q"] as string); - } + // /** + // * @description Get a specific document by uid + // */ + // @Get("/api/v1/notary/documents_notary/:uid", [authHandler, ruleHandler, documentHandler]) + // protected async getOneByUid(req: Request, response: Response) { + // try { + // const uid = req.params["uid"]; + // if (!uid) { + // this.httpBadRequest(response, "No uid provided"); + // return; + // } + // //get query + // let query; + // if (req.query["q"]) { + // query = JSON.parse(req.query["q"] as string); + // } - const documentEntity = await this.documentsNotaryService.getByUid(uid, query); + // const documentEntity = await this.documentsNotaryService.getByUid(uid, query); - if (!documentEntity) { - this.httpNotFoundRequest(response, "document not found"); - return; - } + // if (!documentEntity) { + // this.httpNotFoundRequest(response, "document not found"); + // return; + // } - //Hydrate ressource with prisma entity - const document = Document.hydrate(documentEntity, { strategy: "excludeAll" }); + // //Hydrate ressource with prisma entity + // const document = Document.hydrate(documentEntity, { strategy: "excludeAll" }); - //success - this.httpSuccess(response, document); - } catch (error) { - this.httpInternalError(response, error); - return; - } - } + // //success + // this.httpSuccess(response, document); + // } catch (error) { + // this.httpInternalError(response, error); + // return; + // } + // } } From a68d8b817a70d315b154cbde25f66066e4818343 Mon Sep 17 00:00:00 2001 From: Vins Date: Mon, 9 Sep 2024 15:10:24 +0200 Subject: [PATCH 3/9] Endpoint delete document notary --- .../api/customer/DocumentsNotaryController.ts | 50 +++++++ .../api/notary/DocumentsNotaryController.ts | 138 +++++------------- 2 files changed, 87 insertions(+), 101 deletions(-) create mode 100644 src/app/api/customer/DocumentsNotaryController.ts diff --git a/src/app/api/customer/DocumentsNotaryController.ts b/src/app/api/customer/DocumentsNotaryController.ts new file mode 100644 index 00000000..4fd90e4e --- /dev/null +++ b/src/app/api/customer/DocumentsNotaryController.ts @@ -0,0 +1,50 @@ +import { Response, Request } from "express"; +import { Controller, Get } from "@ControllerPattern/index"; +import ApiController from "@Common/system/controller-pattern/ApiController"; +import { Service } from "typedi"; +import DocumentsNotaryService from "@Services/notary/DocumentsNotaryService/DocumentsNotaryService"; +import { Prisma } from "@prisma/client"; +import { DocumentNotary } from "le-coffre-resources/dist/Notary"; +import authHandler from "@App/middlewares/AuthHandler"; +// import NotificationBuilder from "@Common/notifications/NotificationBuilder"; + +@Controller() +@Service() +export default class DocumentsNotaryController extends ApiController { + constructor( + private documentsNotaryService: DocumentsNotaryService, + ) { + super(); + } + + /** + * @description Get all documents + * @returns IDocument[] list of documents + */ + @Get("/api/v1/customer/documents_notary", [authHandler]) + protected async get(req: Request, response: Response) { + try { + //get query + let query: Prisma.DocumentsNotaryFindManyArgs = {}; + 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; + } + } + + //call service to get prisma entity + const documentEntities = await this.documentsNotaryService.get(query); + + //Hydrate ressource with prisma entity + const documents = DocumentNotary.hydrateArray(documentEntities, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response, documents); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } +} diff --git a/src/app/api/notary/DocumentsNotaryController.ts b/src/app/api/notary/DocumentsNotaryController.ts index 1552646e..afcd826e 100644 --- a/src/app/api/notary/DocumentsNotaryController.ts +++ b/src/app/api/notary/DocumentsNotaryController.ts @@ -1,9 +1,9 @@ import { Response, Request } from "express"; -import { Controller, Get, Post } from "@ControllerPattern/index"; +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 { Prisma } from "@prisma/client"; +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"; @@ -41,10 +41,6 @@ export default class DocumentsNotaryController extends ApiController { return; } } - const officeId: string = req.body.user.office_Id; - const officeWhereInput: Prisma.OfficesWhereInput = { uid: officeId }; - if (!query.where) query.where = { folder: { office: officeWhereInput } }; - query.where.folder!.office = officeWhereInput; //call service to get prisma entity const documentEntities = await this.documentsNotaryService.get(query); @@ -96,112 +92,52 @@ export default class DocumentsNotaryController extends ApiController { const fileEntity = FileNotary.hydrate(JSON.parse(query)); const fileEntityCreated = await this.filesService.createFileNotary(fileEntity, req.file!); - console.log("fileEntityCreated", fileEntityCreated); - - + if(!fileEntityCreated) { + this.httpBadRequest(response, "File could not be created"); + return; + }; - // //init Document resource with request body values - // const documentNotaryEntity = DocumentNotary.hydrate(req.body); - // console.log(documentNotaryEntity); + const documentNotary = await this.documentsNotaryService.getByUid(documentNotaryEntityCreated.uid); - // const folder = await this.officeFoldersService.getByUid(documentNotaryEntity.folder?.uid!, { - // folder_anchor: true, - // }); - // if (!folder) { - // this.httpBadRequest(response, "Folder not found"); - // return; - // } - - // const folderRessource = OfficeFolder.hydrate(folder); - // if (folderRessource.folder_anchor) { - // this.httpBadRequest(response, "Cannot add document on an anchored or anchoring folder"); - // return; - // } - - // //validate document - // await validateOrReject(documentNotaryEntity, { groups: ["createDocument"], forbidUnknownValues: false }); - - // //call service to get prisma entity - // const documentNotaryEntityCreated = await this.documentsNotaryService.create(documentNotaryEntity); - - // //Hydrate ressource with prisma entity - // const documentNotary = DocumentNotary.hydrate(documentNotaryEntityCreated, { - // strategy: "excludeAll", - // }); - - // //success - // this.httpCreated(response, documentNotary); + const document = DocumentNotary.hydrate(documentNotary!); + //success + this.httpCreated(response, document); } catch (error) { this.httpInternalError(response, error); return; } } - // /** - // * @description Delete a specific document - // */ - // @Delete("/api/v1/notary/documents_notary/:uid", [authHandler, ruleHandler, documentHandler]) - // protected async delete(req: Request, response: Response) { - // try { - // const uid = req.params["uid"]; - // if (!uid) { - // this.httpBadRequest(response, "No uid provided"); - // return; - // } + /** + * @description Delete a specific document + */ + @Delete("/api/v1/notary/documents_notary/:uid", [authHandler]) + protected async delete(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } - // const documentFound = await this.documentsNotaryService.getByUid(uid); + const documentNotaryFound = await this.documentsNotaryService.getByUid(uid); - // if (!documentFound) { - // this.httpNotFoundRequest(response, "document not found"); - // return; - // } + if (!documentNotaryFound) { + this.httpNotFoundRequest(response, "document not found"); + return; + } - // //call service to get prisma entity - // const documentEntity: DocumentsNotary = await this.documentsNotaryService.delete(uid); + //call service to get prisma entity + const documentNotaryEntity: DocumentsNotary = await this.documentsNotaryService.delete(uid); - // //Hydrate ressource with prisma entity - // const document = Document.hydrate(documentEntity, { strategy: "excludeAll" }); + //Hydrate ressource with prisma entity + const documentNotary = DocumentNotary.hydrate(documentNotaryEntity, { strategy: "excludeAll" }); - // //success - // this.httpSuccess(response, document); - // } catch (error) { - // this.httpInternalError(response, error); - // return; - // } - // } - - // /** - // * @description Get a specific document by uid - // */ - // @Get("/api/v1/notary/documents_notary/:uid", [authHandler, ruleHandler, documentHandler]) - // protected async getOneByUid(req: Request, response: Response) { - // try { - // const uid = req.params["uid"]; - // if (!uid) { - // this.httpBadRequest(response, "No uid provided"); - // return; - // } - // //get query - // let query; - // if (req.query["q"]) { - // query = JSON.parse(req.query["q"] as string); - // } - - // const documentEntity = await this.documentsNotaryService.getByUid(uid, query); - - // if (!documentEntity) { - // this.httpNotFoundRequest(response, "document not found"); - // return; - // } - - // //Hydrate ressource with prisma entity - // const document = Document.hydrate(documentEntity, { strategy: "excludeAll" }); - - // //success - // this.httpSuccess(response, document); - // } catch (error) { - // this.httpInternalError(response, error); - // return; - // } - // } + //success + this.httpSuccess(response, documentNotary); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } } From 982be25e5b0ef5c653e869a7cbaab1b63edd371c Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 10 Sep 2024 10:14:53 +0200 Subject: [PATCH 4/9] document_name --- .../migrations/20240910081445_document_name/migration.sql | 2 ++ src/common/databases/schema.prisma | 1 + 2 files changed, 3 insertions(+) create mode 100644 src/common/databases/migrations/20240910081445_document_name/migration.sql diff --git a/src/common/databases/migrations/20240910081445_document_name/migration.sql b/src/common/databases/migrations/20240910081445_document_name/migration.sql new file mode 100644 index 00000000..97e5f5c0 --- /dev/null +++ b/src/common/databases/migrations/20240910081445_document_name/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "documents_notary" ADD COLUMN "document_name" VARCHAR(255) NOT NULL DEFAULT ''; diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index a88f4839..52aa353e 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -234,6 +234,7 @@ model DocumentsNotary { files FilesNotary[] customer Customers @relation(fields: [customer_uid], references: [uid], onDelete: Cascade) customer_uid String @db.VarChar(255) + document_name String @db.VarChar(255) @default("") @@map("documents_notary") } From 41efb43543bdfc5063e5a1b13ce8b97cf5fb4afe Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 10 Sep 2024 10:15:53 +0200 Subject: [PATCH 5/9] name --- .../migrations/20240910081547_name/migration.sql | 9 +++++++++ src/common/databases/schema.prisma | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 src/common/databases/migrations/20240910081547_name/migration.sql diff --git a/src/common/databases/migrations/20240910081547_name/migration.sql b/src/common/databases/migrations/20240910081547_name/migration.sql new file mode 100644 index 00000000..18e8d895 --- /dev/null +++ b/src/common/databases/migrations/20240910081547_name/migration.sql @@ -0,0 +1,9 @@ +/* + Warnings: + + - You are about to drop the column `document_name` on the `documents_notary` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "documents_notary" DROP COLUMN "document_name", +ADD COLUMN "document" VARCHAR(255) NOT NULL DEFAULT ''; diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index 52aa353e..5007857f 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -234,7 +234,7 @@ model DocumentsNotary { files FilesNotary[] customer Customers @relation(fields: [customer_uid], references: [uid], onDelete: Cascade) customer_uid String @db.VarChar(255) - document_name String @db.VarChar(255) @default("") + document String @db.VarChar(255) @default("") @@map("documents_notary") } From 5bd5e096cac95fbe38c1b1695bc48035c6b130e6 Mon Sep 17 00:00:00 2001 From: Max S Date: Tue, 10 Sep 2024 18:11:10 +0200 Subject: [PATCH 6/9] 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 From bcd12b5317a3816235e613bb71030b14625b3a88 Mon Sep 17 00:00:00 2001 From: Max S Date: Tue, 10 Sep 2024 18:16:32 +0200 Subject: [PATCH 7/9] remove console log --- src/app/api/notary/CustomersController.ts | 24 ++++++++++------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/app/api/notary/CustomersController.ts b/src/app/api/notary/CustomersController.ts index 8dc1e1c9..ebb1b541 100644 --- a/src/app/api/notary/CustomersController.ts +++ b/src/app/api/notary/CustomersController.ts @@ -220,7 +220,7 @@ export default class CustomersController extends ApiController { /** * @description Send a reminder to a specific customer by uid to signe specific documents */ - @Post("/api/v1/notary/customers/:uid/send_reminder", [authHandler, ruleHandler, customerHandler]) + @Post("/api/v1/notary/customers/:uid/send_reminder", [authHandler, ruleHandler, customerHandler]) protected async sendReminder(req: Request, response: Response) { try { const uid = req.params["uid"]; @@ -228,18 +228,18 @@ export default class CustomersController extends ApiController { this.httpBadRequest(response, "No uid provided"); return; } - + const documentsUid = req.body.documentsUid; if (!documentsUid || !Array.isArray(documentsUid)) { this.httpBadRequest(response, "Invalid or missing documents"); return; } - const documentEntities : Documents[] = []; + 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) { - const documentEntity = await this.documentsService.getByUid(documentUid, { document_type : true, folder : true }); - + const documentEntity = await this.documentsService.getByUid(documentUid, { document_type: true, folder: true }); + if (!documentEntity) { this.httpBadRequest(response, "Document not found"); return; @@ -247,9 +247,8 @@ export default class CustomersController extends ApiController { documentEntities.push(documentEntity); } - const customerEntity = await this.customersService.getByUid(uid, { contact: true, office : true }); - - + const customerEntity = await this.customersService.getByUid(uid, { contact: true, office: true }); + if (!customerEntity) { this.httpNotFoundRequest(response, "customer not found"); return; @@ -257,13 +256,10 @@ export default class CustomersController extends ApiController { //Hydrate ressource with prisma entity const customer = Customer.hydrate(customerEntity, { strategy: "excludeAll" }); - - - // Call service to send reminder with documents - const reminder = await this.customersService.sendDocumentsReminder(customer, documentEntities); - - + // Call service to send reminder with documents + await this.customersService.sendDocumentsReminder(customer, documentEntities); + // Success this.httpSuccess(response, customer); } catch (error) { From 95f2c8a8bc04f638e3f2806d6271005504dc3915 Mon Sep 17 00:00:00 2001 From: Max S Date: Tue, 10 Sep 2024 18:38:15 +0200 Subject: [PATCH 8/9] :truck: delete filesNotaryHandler middleware --- src/app/api/notary/FilesNotaryController.ts | 5 +- .../FilesNotaryHandler.ts | 48 ------------------- 2 files changed, 2 insertions(+), 51 deletions(-) delete mode 100644 src/app/middlewares/OfficeMembershipHandlers/FilesNotaryHandler.ts diff --git a/src/app/api/notary/FilesNotaryController.ts b/src/app/api/notary/FilesNotaryController.ts index 2fb116e2..9b7e6742 100644 --- a/src/app/api/notary/FilesNotaryController.ts +++ b/src/app/api/notary/FilesNotaryController.ts @@ -1,5 +1,4 @@ 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"; @@ -52,7 +51,7 @@ export default class FilesNotaryController extends ApiController { /** * @description Get a specific File by uid */ - @Get("/api/v1/notary/files-notary/download/:uid", [authHandler, FilesNotaryHandler]) + @Get("/api/v1/notary/files-notary/download/:uid", [authHandler]) protected async download(req: Request, response: Response) { const uid = req.params["uid"]; if (!uid) { @@ -80,7 +79,7 @@ export default class FilesNotaryController extends ApiController { /** * @description Get a specific File by uid */ - @Get("/api/v1/notary/files-notary/:uid", [authHandler, FilesNotaryHandler]) + @Get("/api/v1/notary/files-notary/:uid", [authHandler]) protected async getOneByUid(req: Request, response: Response) { try { const uid = req.params["uid"]; diff --git a/src/app/middlewares/OfficeMembershipHandlers/FilesNotaryHandler.ts b/src/app/middlewares/OfficeMembershipHandlers/FilesNotaryHandler.ts deleted file mode 100644 index b3453183..00000000 --- a/src/app/middlewares/OfficeMembershipHandlers/FilesNotaryHandler.ts +++ /dev/null @@ -1,48 +0,0 @@ -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; - } -} From bb9547be9bb8761650fadb2cd31869b4b927bc96 Mon Sep 17 00:00:00 2001 From: Max S Date: Wed, 11 Sep 2024 10:51:18 +0200 Subject: [PATCH 9/9] rename filesNotaryService --- src/app/api/notary/DocumentsNotaryController.ts | 2 +- src/app/api/notary/FilesNotaryController.ts | 3 ++- .../{FilesService.ts => FilesNotaryService.ts} | 0 3 files changed, 3 insertions(+), 2 deletions(-) rename src/services/common/FilesNotaryService/{FilesService.ts => FilesNotaryService.ts} (100%) diff --git a/src/app/api/notary/DocumentsNotaryController.ts b/src/app/api/notary/DocumentsNotaryController.ts index 56ea9cdf..900a54d5 100644 --- a/src/app/api/notary/DocumentsNotaryController.ts +++ b/src/app/api/notary/DocumentsNotaryController.ts @@ -3,7 +3,7 @@ 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 FilesNotaryService from "@Services/common/FilesNotaryService/FilesNotaryService"; import DocumentsNotaryService from "@Services/notary/DocumentsNotaryService/DocumentsNotaryService"; import OfficeFoldersService from "@Services/notary/OfficeFoldersService/OfficeFoldersService"; import UsersService from "@Services/notary/UsersService/UsersService"; diff --git a/src/app/api/notary/FilesNotaryController.ts b/src/app/api/notary/FilesNotaryController.ts index 9b7e6742..acda6176 100644 --- a/src/app/api/notary/FilesNotaryController.ts +++ b/src/app/api/notary/FilesNotaryController.ts @@ -2,7 +2,8 @@ import authHandler from "@App/middlewares/AuthHandler"; 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 FilesNotaryService from "@Services/common/FilesNotaryService/FilesNotaryService"; + import { Request, Response } from "express"; import { File } from "le-coffre-resources/dist/Notary"; import { Service } from "typedi"; diff --git a/src/services/common/FilesNotaryService/FilesService.ts b/src/services/common/FilesNotaryService/FilesNotaryService.ts similarity index 100% rename from src/services/common/FilesNotaryService/FilesService.ts rename to src/services/common/FilesNotaryService/FilesNotaryService.ts