Merge branch 'dev' into staging

This commit is contained in:
Max S 2024-09-11 12:44:30 +02:00
commit 948ce03e34
25 changed files with 1073 additions and 47 deletions

View File

@ -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",

View File

@ -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<DocumentNotary>(documentEntities, { strategy: "excludeAll" });
//success
this.httpSuccess(response, documents);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
}

View File

@ -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,55 @@ 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) {
const documentEntity = await this.documentsService.getByUid(documentUid, { document_type: true, folder: true });
if (!documentEntity) {
this.httpBadRequest(response, "Document not found");
return;
}
documentEntities.push(documentEntity);
}
const customerEntity = await this.customersService.getByUid(uid, { contact: true, office: true });
if (!customerEntity) {
this.httpNotFoundRequest(response, "customer not found");
return;
}
//Hydrate ressource with prisma entity
const customer = Customer.hydrate<Customer>(customerEntity, { strategy: "excludeAll" });
// Call service to send reminder with documents
await this.customersService.sendDocumentsReminder(customer, documentEntities);
// Success
this.httpSuccess(response, customer);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
}

View File

@ -0,0 +1,137 @@
import authHandler from "@App/middlewares/AuthHandler";
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/FilesNotaryService";
import DocumentsNotaryService from "@Services/notary/DocumentsNotaryService/DocumentsNotaryService";
import OfficeFoldersService from "@Services/notary/OfficeFoldersService/OfficeFoldersService";
import UsersService from "@Services/notary/UsersService/UsersService";
import { Request, Response } from "express";
import { DocumentNotary, FileNotary } from "le-coffre-resources/dist/Notary";
import { Service } from "typedi";
@Controller()
@Service()
export default class DocumentsNotaryController extends ApiController {
constructor(
private documentsNotaryService: DocumentsNotaryService,
private officeFoldersService: OfficeFoldersService,
private customerService: CustomersService,
private userService: UsersService,
private filesNotaryService: FilesNotaryService,
) {
super();
}
/**
* @description Get all documents
* @returns IDocument[] list of documents
*/
@Get("/api/v1/notary/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<DocumentNotary>(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;
const folder = await this.officeFoldersService.getByUid(req.body.folderUid);
if (!folder) return;
const user = await this.userService.getByUid(req.body.user.userId);
if (!user) return;
const documentNotaryEntity = DocumentNotary.hydrate<DocumentNotary>({
customer: customer,
folder: folder,
depositor: user,
});
const documentNotaryEntityCreated = await this.documentsNotaryService.create(documentNotaryEntity);
const query = JSON.stringify({ document: { uid: documentNotaryEntityCreated.uid } });
const fileEntity = FileNotary.hydrate<FileNotary>(JSON.parse(query));
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);
const document = DocumentNotary.hydrate<DocumentNotary>(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])
protected async delete(req: Request, response: Response) {
try {
const uid = req.params["uid"];
if (!uid) {
this.httpBadRequest(response, "No uid provided");
return;
}
const documentNotaryFound = await this.documentsNotaryService.getByUid(uid);
if (!documentNotaryFound) {
this.httpNotFoundRequest(response, "document not found");
return;
}
//call service to get prisma entity
const documentNotaryEntity: DocumentsNotary = await this.documentsNotaryService.delete(uid);
//Hydrate ressource with prisma entity
const documentNotary = DocumentNotary.hydrate<DocumentNotary>(documentNotaryEntity, { strategy: "excludeAll" });
//success
this.httpSuccess(response, documentNotary);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
}

View File

@ -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<DocumentReminder>(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<Document>(documentEntity, { strategy: "excludeAll" });
// //success
// this.httpSuccess(response, document);
// } catch (error) {
// this.httpInternalError(response, error);
// return;
// }
// }
}

View File

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

View File

@ -0,0 +1,109 @@
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/FilesNotaryService";
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<File>(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])
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])
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<File>(fileEntity, { strategy: "excludeAll" });
//success
this.httpSuccess(response, file);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
}

View File

@ -55,6 +55,9 @@ 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";
import FilesNotaryController from "./api/notary/FilesNotaryController";
/**
* @description This allow to declare all controllers used in the application
@ -118,5 +121,8 @@ export default {
Container.get(RulesGroupsController);
Container.get(NotesController);
Container.get(MailchimpController);
Container.get(DocumentsReminderController);
Container.get(DocumentsNotaryController);
Container.get(FilesNotaryController);
},
};

View File

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

View File

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

View File

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

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "documents_notary" ADD COLUMN "document_name" VARCHAR(255) NOT NULL DEFAULT '';

View File

@ -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 '';

View File

@ -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,38 @@ 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)
document String @db.VarChar(255) @default("")
@@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 +279,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)

View File

@ -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,
});
}
}

View File

@ -3,4 +3,5 @@ export const ETemplates = {
DOCUMENT_REFUSED: "DOCUMENT_REFUSED",
DOCUMENT_RECAP: "DOCUMENT_RECAP",
SUBSCRIPTION_INVITATION: "SUBSCRIPTION_INVITATION",
DOCUMENT_REMINDER: "DOCUMENT_REMINDER",
};

View File

@ -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<DocumentsNotary> {
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<DocumentsNotary> {
return this.model.delete({
where: {
uid: uid,
},
});
}
/**
* @description : Find unique document
*/
public async findOneByUid(uid: string, query?: Prisma.DocumentsNotaryInclude): Promise<DocumentsNotary | null> {
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 },
});
}
}

View File

@ -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<DocumentsReminder> {
const createArgs: Prisma.DocumentsReminderCreateArgs = {
data: {
document: {
connect: {
uid: documentReminder.document!.uid,
},
},
reminder_date: new Date(),
},
};
const documentReminderCreated = await this.model.create({ ...createArgs });
return documentReminderCreated;
}
}

View File

@ -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<FilesNotary> {
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 },
});
}
}

View File

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

View File

@ -1,13 +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";
@Service()
export default class FilesService extends BaseService {

View File

@ -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<Office>(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,7 +289,7 @@ 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<Office>(officeInfos!);
const searchParams = new URLSearchParams({
@ -339,7 +337,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 +376,17 @@ export default class IdNotService extends BaseService {
},
};
if(!userToAdd.contact.email) {
if (!userToAdd.contact.email) {
return null;
}
let userHydrated = User.hydrate<User>(userToAdd);
const user = await this.userService.create(userHydrated);
const userOffice = await this.officeService.getByUid(user.office_uid);
userHydrated = User.hydrate<User>(user);
const userOfficeHydrated = Office.hydrate<Office>(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 +399,16 @@ export default class IdNotService extends BaseService {
where: { office: { idNot: "0000" } },
include: { office: true },
});
const officeRolesHydrated = OfficeRole.hydrateArray<OfficeRole>(officeRoles);
const deedTypesHydrated = DeedType.hydrateArray<DeedType>(deedTypes);
const documentTypesHydrated = DocumentType.hydrateArray<DocumentType>(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 +424,35 @@ export default class IdNotService extends BaseService {
public async duplicateDocumentTypes(documentTypes: DocumentType[], office: Office): Promise<DocumentType[]> {
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<DocumentType>(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<User>(usersReq);

View File

@ -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<Customers | null> {
return this.customerRepository.findOneByContact(contactUid);
}
public async sendDocumentsReminder(customer: Customer, documents: Documents[]): Promise<void> {
//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);
}
}
}

View File

@ -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<DocumentsNotary> {
return this.documentsNotaryRepository.create(document);
}
/**
* @description : Delete a document
* @throws {Error} If document cannot be deleted
*/
public async delete(uid: string): Promise<DocumentsNotary> {
const documentEntity = await this.documentsNotaryRepository.findOneByUid(uid, { files: true });
if (!documentEntity) throw new Error("document not found");
const document = Document.hydrate<Document>(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<DocumentsNotary | null> {
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);
}
}

View File

@ -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<DocumentsReminder> {
return this.documentsReminderRepository.create(document);
}
}