Merge Staging in Prepod

This commit is contained in:
Arnaud D. Natali 2023-10-10 15:19:53 +02:00 committed by GitHub
commit 9b493dd075
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 613 additions and 211 deletions

View File

@ -1,9 +0,0 @@
const {join} = require('path');
/**
* @type {import("puppeteer").Configuration}
*/
module.exports = {
// Changes the cache location for Puppeteer.
cacheDirectory: join(__dirname, '.cache', 'puppeteer'),
};

View File

@ -1,14 +1,19 @@
# Install dependencies only when needed
FROM node:19-alpine AS deps
FROM node:18-bullseye-slim AS deps
WORKDIR leCoffre
RUN npm install -D prisma@4.11.0
COPY package.json ./
RUN apk update && apk add openssh-client git
# ENV PUPPETEER_CACHE_DIR=~/leCoffre/.cache/puppeteer
# ENV PUPPETEER_SKIP_DOWNLOAD=false
RUN apt-get update \
&& apt-get install -y wget gnupg \
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/googlechrome-linux-keyring.gpg \
&& sh -c 'echo "deb [arch=amd64 signed-by=/usr/share/keyrings/googlechrome-linux-keyring.gpg] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
&& apt-get update \
&& apt-get install -y google-chrome-stable chromium fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-khmeros fonts-kacst fonts-freefont-ttf libxss1 openssh-client git \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
COPY id_rsa /root/.ssh/id_rsa
RUN chmod 600 ~/.ssh/id_rsa
@ -18,13 +23,12 @@ RUN ssh-keyscan github.com smart-chain-fr/leCoffre-resources.git >> /root/.ssh/k
RUN npm install --frozen-lockfile
# Rebuild the source code only when needed
FROM node:19-alpine AS builder
FROM node:18-bullseye-slim AS builder
WORKDIR leCoffre
COPY --from=deps leCoffre/node_modules ./node_modules
COPY --from=deps leCoffre/package.json package.json
#COPY --from=deps leCoffre/.cache ./.cache
COPY tsconfig.json tsconfig.json
COPY src src
@ -33,19 +37,24 @@ RUN npx prisma generate
RUN npm run build
# Production image, copy all the files and run next
FROM node:19-alpine AS production
FROM node:18-bullseye-slim AS production
WORKDIR leCoffre
RUN adduser -D lecoffreuser --uid 10000 && chown -R lecoffreuser .
RUN adduser --disabled-password lecoffreuser --uid 10000 && chown -R lecoffreuser .
RUN mkdir -p /home/lecoffreuser/Downloads \
&& mkdir -p /home/lecoffreuser/Downloads \
&& chown -R lecoffreuser:lecoffreuser /home/lecoffreuser
COPY --from=builder --chown=lecoffreuser leCoffre/node_modules ./node_modules
COPY --from=builder --chown=lecoffreuser leCoffre/dist dist
COPY --from=builder --chown=lecoffreuser leCoffre/package.json ./package.json
COPY --from=builder --chown=lecoffreuser leCoffre/src/common/databases ./src/common/databases
#COPY --from=builder --chown=lecoffreuser leCoffre/.cache ./.cache
USER lecoffreuser
CMD ["npm", "run", "start"]
EXPOSE 3001

View File

@ -28,6 +28,9 @@ lecoffreBack:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/from-to-www-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/client_max_body_size: 200m
nginx.ingress.kubernetes.io/client-body-buffer-size: 2M
nginx.ingress.kubernetes.io/proxy-body-size: 200m
env:
- key: .env
scwID: "id:430001f8-68ab-47b2-92e8-38024c35a80d"

View File

@ -28,6 +28,9 @@ lecoffreBack:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/from-to-www-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/client_max_body_size: 200m
nginx.ingress.kubernetes.io/client-body-buffer-size: 2M
nginx.ingress.kubernetes.io/proxy-body-size: 200m
env:
- key: .env
scwID: "id:8f66af26-2481-4ef2-b4f0-7f076f9ee18b"

View File

@ -28,6 +28,9 @@ lecoffreBack:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/from-to-www-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/client_max_body_size: 200m
nginx.ingress.kubernetes.io/client-body-buffer-size: 2M
nginx.ingress.kubernetes.io/proxy-body-size: 200m
# key is name of the environment variable, scwID is the secret ID in SCW with "id:" in front
env:
- key: .env

View File

@ -53,7 +53,7 @@
"express": "^4.18.2",
"fp-ts": "^2.16.1",
"jsonwebtoken": "^9.0.0",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.89",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.90",
"module-alias": "^2.2.2",
"monocle-ts": "^2.3.13",
"multer": "^1.4.5-lts.1",

View File

@ -9,6 +9,7 @@ import authHandler from "@App/middlewares/AuthHandler";
import ruleHandler from "@App/middlewares/RulesHandler";
import roleHandler from "@App/middlewares/RolesHandler";
import { Prisma } from "@prisma/client";
import customerHandler from "@App/middlewares/OfficeMembershipHandlers/CustomerHandler";
@Controller()
@Service()
@ -78,7 +79,7 @@ export default class CustomersController extends ApiController {
/**
* @description Modify a specific customer by uid
*/
@Put("/api/v1/admin/customers/:uid", [authHandler, roleHandler, ruleHandler])
@Put("/api/v1/admin/customers/:uid", [authHandler, roleHandler, ruleHandler, customerHandler])
protected async put(req: Request, response: Response) {
try {
const uid = req.params["uid"];
@ -119,7 +120,7 @@ export default class CustomersController extends ApiController {
/**
* @description Get a specific customer by uid
*/
@Get("/api/v1/admin/customers/:uid", [authHandler, roleHandler, ruleHandler])
@Get("/api/v1/admin/customers/:uid", [authHandler, roleHandler, ruleHandler, customerHandler])
protected async getOneByUid(req: Request, response: Response) {
try {
const uid = req.params["uid"];

View File

@ -3,7 +3,7 @@ import { Controller, Delete, Get, Post, Put } from "@ControllerPattern/index";
import ApiController from "@Common/system/controller-pattern/ApiController";
import { Service } from "typedi";
import DocumentsService from "@Services/admin/DocumentsService/DocumentsService";
import { Documents, Prisma } from "@prisma/client";
import { Documents, EDocumentStatus, Prisma } from "@prisma/client";
import { Document } from "le-coffre-resources/dist/Admin";
import { validateOrReject } from "class-validator";
import authHandler from "@App/middlewares/AuthHandler";
@ -31,8 +31,8 @@ export default class DocumentsController extends ApiController {
query = JSON.parse(req.query["q"] as string);
}
const officeId: string = req.body.user.office_Id;
const officeWhereInput: Prisma.OfficesWhereInput = { uid: officeId } ;
if(!query.where) query.where = { document_type : {office: officeWhereInput}};
const officeWhereInput: Prisma.OfficesWhereInput = { uid: officeId };
if (!query.where) query.where = { document_type: { office: officeWhereInput } };
query.where.document_type!.office = officeWhereInput;
//call service to get prisma entity
@ -104,7 +104,55 @@ export default class DocumentsController extends ApiController {
await validateOrReject(documentEntity, { groups: ["updateDocument"] });
//call service to get prisma entity
const documentEntityUpdated: Documents = await this.documentsService.update(uid, documentEntity, req.body.refused_reason);
const documentEntityUpdated: Documents = await this.documentsService.update(uid, documentEntity);
//Hydrate ressource with prisma entity
const document = Document.hydrate<Document>(documentEntityUpdated, { strategy: "excludeAll" });
//success
this.httpSuccess(response, document);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
/**
* @description Update a specific document
*/
@Put("/api/v1/notary/documents/:uid/refuse", [authHandler, ruleHandler, documentHandler])
protected async refuseDocument(req: Request, response: Response) {
try {
const uid = req.params["uid"];
if (!uid) {
this.httpBadRequest(response, "No uid provided");
return;
}
const documentFound = await this.documentsService.getByUid(uid, {
files: true,
});
if (!documentFound) {
this.httpNotFoundRequest(response, "document not found");
return;
}
//init Document resource with request body values
const documentEntity = Document.hydrate<Document>(documentFound);
// Status to refuse
documentEntity.document_status = EDocumentStatus.REFUSED;
//validate document
await validateOrReject(documentEntity, { groups: ["updateDocument"] });
//call service to get prisma entity
const documentEntityUpdated: Documents = await this.documentsService.refuse(uid, documentEntity, req.body.refused_reason);
//create email for asked document
// this.emailBuilder.sendDocumentEmails(documentEntityUpdated);
// this.notificationBuilder.sendDocumentAnchoredNotificatiom(documentEntityUpdated);
//Hydrate ressource with prisma entity
const document = Document.hydrate<Document>(documentEntityUpdated, { strategy: "excludeAll" });

View File

@ -58,10 +58,10 @@ export default class OfficeFoldersController extends ApiController {
};
}
const officeId: string = req.body.user.office_Id;
const officeWhereInput: Prisma.OfficesWhereInput = { uid: officeId } ;
if(!query.where) query.where = { office: officeWhereInput};
query.where.office = officeWhereInput;
const userId: string = req.body.user.userId;
if (query.where?.stakeholders) delete query.where.stakeholders;
const officeFoldersWhereInput: Prisma.OfficeFoldersWhereInput = { ...query.where, stakeholders: { some: { uid: userId } } };
query.where = officeFoldersWhereInput;
//call service to get prisma entity
const officeFolderEntities: OfficeFolders[] = await this.officeFoldersService.get(query);

View File

@ -8,11 +8,13 @@ import { Document } from "le-coffre-resources/dist/Customer";
import authHandler from "@App/middlewares/AuthHandler";
import documentHandler from "@App/middlewares/CustomerHandler/DocumentHandler";
import { validateOrReject } from "class-validator";
import OfficeFoldersService from "@Services/super-admin/OfficeFoldersService/OfficeFoldersService";
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
@Controller()
@Service()
export default class DocumentsController extends ApiController {
constructor(private documentsService: DocumentsService) {
constructor(private documentsService: DocumentsService, private officeFoldersService: OfficeFoldersService) {
super();
}
@ -94,7 +96,22 @@ export default class DocumentsController extends ApiController {
try {
//init Document resource with request body values
const documentEntity = Document.hydrate<Document>(req.body);
if(!documentEntity.folder?.uid) {
this.httpBadRequest(response, "No folder uid provided");
return;
}
const folder = await this.officeFoldersService.getByUid(documentEntity.folder.uid, {folder_anchor: true});
if(!folder) {
this.httpBadRequest(response, "Folder not found");
return;
}
const folderEntity = OfficeFolder.hydrate<OfficeFolder>(folder);
if (folderEntity.folder_anchor?.status === "VERIFIED_ON_CHAIN") {
this.httpBadRequest(response, "Cannot update a verified folder");
return;
}
//validate document
await validateOrReject(documentEntity, { groups: ["createDocument"], forbidUnknownValues: false });

View File

@ -6,19 +6,16 @@ import FilesService from "@Services/common/FilesService/FilesService";
import { Files, Prisma } from "@prisma/client";
import { File } from "le-coffre-resources/dist/Customer";
import { Document } from "le-coffre-resources/dist/Customer";
import { validateOrReject } from "class-validator";
import DocumentsService from "@Services/customer/DocumentsService/DocumentsService";
import authHandler from "@App/middlewares/AuthHandler";
import fileHandler from "@App/middlewares/CustomerHandler/FileHandler";
import DocumentTypesService from "@Services/super-admin/DocumentTypesService/DocumentTypesService";
import { DocumentType } from "le-coffre-resources/dist/SuperAdmin";
import ObjectHydrate from "@Common/helpers/ObjectHydrate";
import NotificationBuilder from "@Common/notifications/NotificationBuilder";
import { validateOrReject } from "class-validator";
@Controller()
@Service()
export default class FilesController extends ApiController {
constructor(private filesService: FilesService, private documentService: DocumentsService, private documentTypesService : DocumentTypesService, private notificationBuilder : NotificationBuilder) {
constructor(private filesService: FilesService, private documentService: DocumentsService, private notificationBuilder : NotificationBuilder) {
super();
}
@ -93,7 +90,12 @@ export default class FilesController extends ApiController {
const fileEntity = File.hydrate<File>(JSON.parse(req.body["q"]));
//validate File
// await validateOrReject(fileEntity, { groups: ["createFile"] });
try {
await validateOrReject(fileEntity, { groups: ["createFile"] });
} catch (error) {
this.httpBadRequest(response, error);
return;
}
//call service to get prisma entity
const fileEntityCreated = await this.filesService.create(fileEntity, req.file);
@ -111,6 +113,7 @@ export default class FilesController extends ApiController {
strategy: "excludeAll",
});
//success
this.httpCreated(response, fileEntityHydrated);
} catch (error) {
@ -226,52 +229,4 @@ export default class FilesController extends ApiController {
return;
}
}
/**
* @description Create a new File
* @returns File created
*/
@Post("/api/v1/customer/addPersonalFile", [authHandler, fileHandler])
protected async addPersonalFile(req: Request, response: Response) {
try {
//get file
if (!req.file) throw new Error("No file provided");
//init File resource with request body values
const fileEntity = File.hydrate<File>(JSON.parse(req.body["q"]));
const documentTypeEntities = await this.documentTypesService.get({ where: { name: "Other"} });
const documentTypeEntity = documentTypeEntities[0];
const documentType = ObjectHydrate.hydrate<DocumentType>(new DocumentType(), documentTypeEntity!, { strategy: "excludeAll" });
const documentEntity = Document.hydrate<Document>({document_type: documentType});
await validateOrReject(documentEntity, { groups: ["createDocument"], forbidUnknownValues: false });
const documentEntityCreated = await this.documentService.create(documentEntity);
const document = Document.hydrate<Document>(documentEntityCreated, {
strategy: "excludeAll",
});
fileEntity.document = document;
const fileEntityCreated = await this.filesService.create(fileEntity, req.file);
const documentToUpdate = Document.hydrate<Document>(document!);
documentToUpdate!.document_status = "DEPOSITED";
await this.documentService.update(document!.uid!, documentToUpdate);
//Hydrate ressource with prisma entity
const fileEntityHydrated = File.hydrate<File>(fileEntityCreated, {
strategy: "excludeAll",
});
//success
this.httpCreated(response, fileEntityHydrated);
} catch (error) {
this.httpBadRequest(response, error);
return;
}
}
}

View File

@ -2,7 +2,7 @@ import { Response, Request } from "express";
import { Controller, Post } from "@ControllerPattern/index";
import ApiController from "@Common/system/controller-pattern/ApiController";
import { Service } from "typedi";
import Id360Service from "@Services/common/Id360Service/Id360Service";
import Id360Service, { EnrollmentResponse } from "@Services/common/Id360Service/Id360Service";
import CustomersService from "@Services/customer/CustomersService/CustomersService";
import AuthService from "@Services/common/AuthService/AuthService";
import { Customer } from "le-coffre-resources/dist/SuperAdmin";
@ -34,7 +34,11 @@ export default class CustomerController extends ApiController {
return;
}
try {
const enrollment = await this.id360Service.getEnrollment(callbackToken);
const res = await this.id360Service.getEnrollment(callbackToken);
const enrollment = await res.json() as EnrollmentResponse;
if(enrollment.status === "STARTED") {
this.loginCallback(req, response);
}
if (enrollment.status !== "OK") {
this.httpUnauthorized(response, "Enrollment status is not OK");
return;

View File

@ -20,7 +20,6 @@ export default class DocumentController extends ApiController {
protected async getDocumentVerificationFromId360(req: Request, response: Response) {
try {
console.log("document callback", req, response)
this.httpSuccess(response);
} catch (error) {
console.log(error);
@ -33,7 +32,6 @@ export default class DocumentController extends ApiController {
protected async getCustomerVerificationFromId360(req: Request, response: Response) {
try {
console.log("customer callback", req, response)
this.httpSuccess(response);
} catch (error) {
console.log(error);

View File

@ -25,6 +25,10 @@ export default class UserController extends ApiController {
if (!code) throw new Error("code is required");
const idNotToken = await this.idNotService.getIdNotToken(code);
if(!idNotToken) {
this.httpValidationError(response, "IdNot token undefined");
return;
}
const user = await this.idNotService.getOrCreateUser(idNotToken);
if(!user) {

View File

@ -8,6 +8,7 @@ import { validateOrReject } from "class-validator";
import authHandler from "@App/middlewares/AuthHandler";
import ruleHandler from "@App/middlewares/RulesHandler";
import { Prisma } from "@prisma/client";
import customerHandler from "@App/middlewares/OfficeMembershipHandlers/CustomerHandler";
@Controller()
@Service()
@ -76,7 +77,7 @@ export default class CustomersController extends ApiController {
/**
* @description Modify a specific customer by uid
*/
@Put("/api/v1/notary/customers/:uid", [authHandler, ruleHandler])
@Put("/api/v1/notary/customers/:uid", [authHandler, ruleHandler, customerHandler])
protected async put(req: Request, response: Response) {
try {
const uid = req.params["uid"];
@ -117,7 +118,7 @@ export default class CustomersController extends ApiController {
/**
* @description Get a specific customer by uid
*/
@Get("/api/v1/notary/customers/:uid", [authHandler, ruleHandler])
@Get("/api/v1/notary/customers/:uid", [authHandler, ruleHandler, customerHandler])
protected async getOneByUid(req: Request, response: Response) {
try {
const uid = req.params["uid"];

View File

@ -3,19 +3,24 @@ import { Controller, Delete, Get, Post, Put } from "@ControllerPattern/index";
import ApiController from "@Common/system/controller-pattern/ApiController";
import { Service } from "typedi";
import DocumentsService from "@Services/notary/DocumentsService/DocumentsService";
import { Documents, Prisma } from "@prisma/client";
import { Document } from "le-coffre-resources/dist/Notary";
import { Documents, EDocumentStatus, Prisma } from "@prisma/client";
import { Document, OfficeFolder } from "le-coffre-resources/dist/Notary";
import { validateOrReject } from "class-validator";
import authHandler from "@App/middlewares/AuthHandler";
import ruleHandler from "@App/middlewares/RulesHandler";
import documentHandler from "@App/middlewares/OfficeMembershipHandlers/DocumentHandler";
import EmailBuilder from "@Common/emails/EmailBuilder";
import OfficeFoldersService from "@Services/notary/OfficeFoldersService/OfficeFoldersService";
// import NotificationBuilder from "@Common/notifications/NotificationBuilder";
@Controller()
@Service()
export default class DocumentsController extends ApiController {
constructor(private documentsService: DocumentsService, private emailBuilder: EmailBuilder) {
constructor(
private documentsService: DocumentsService,
private emailBuilder: EmailBuilder,
private officeFoldersService: OfficeFoldersService,
) {
super();
}
@ -32,8 +37,8 @@ export default class DocumentsController extends ApiController {
query = JSON.parse(req.query["q"] as string);
}
const officeId: string = req.body.user.office_Id;
const officeWhereInput: Prisma.OfficesWhereInput = { uid: officeId } ;
if(!query.where) query.where = { document_type : {office: officeWhereInput}};
const officeWhereInput: Prisma.OfficesWhereInput = { uid: officeId };
if (!query.where) query.where = { document_type: { office: officeWhereInput } };
query.where.document_type!.office = officeWhereInput;
//call service to get prisma entity
@ -60,6 +65,20 @@ export default class DocumentsController extends ApiController {
//init Document resource with request body values
const documentEntity = Document.hydrate<Document>(req.body);
const folder = await this.officeFoldersService.getByUid(documentEntity.folder?.uid!, {
folder_anchor: true,
});
if (!folder) {
this.httpBadRequest(response, "Folder not found");
return;
}
const folderRessource = OfficeFolder.hydrate<OfficeFolder>(folder);
if (folderRessource.folder_anchor) {
this.httpBadRequest(response, "Cannot ask document on an anchored or anchoring folder");
return;
}
//validate document
await validateOrReject(documentEntity, { groups: ["createDocument"], forbidUnknownValues: false });
@ -104,13 +123,59 @@ export default class DocumentsController extends ApiController {
//init Document resource with request body values
const documentEntity = Document.hydrate<Document>(req.body);
//validate document
await validateOrReject(documentEntity, { groups: ["updateDocument"] });
//call service to get prisma entity
const documentEntityUpdated: Documents = await this.documentsService.update(uid, documentEntity);
//create email for asked document
// this.emailBuilder.sendDocumentEmails(documentEntityUpdated);
// this.notificationBuilder.sendDocumentAnchoredNotificatiom(documentEntityUpdated);
//Hydrate ressource with prisma entity
const document = Document.hydrate<Document>(documentEntityUpdated, { strategy: "excludeAll" });
//success
this.httpSuccess(response, document);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
/**
* @description Update a specific document
*/
@Put("/api/v1/notary/documents/:uid/refuse", [authHandler, ruleHandler, documentHandler])
protected async refuseDocument(req: Request, response: Response) {
try {
const uid = req.params["uid"];
if (!uid) {
this.httpBadRequest(response, "No uid provided");
return;
}
const documentFound = await this.documentsService.getByUid(uid, {
files: true,
});
if (!documentFound) {
this.httpNotFoundRequest(response, "document not found");
return;
}
//init Document resource with request body values
const documentEntity = Document.hydrate<Document>(documentFound);
// Status to refuse
documentEntity.document_status = EDocumentStatus.REFUSED;
//validate document
await validateOrReject(documentEntity, { groups: ["updateDocument"] });
//call service to get prisma entity
const documentEntityUpdated: Documents = await this.documentsService.update(uid, documentEntity, req.body.refused_reason);
const documentEntityUpdated: Documents = await this.documentsService.refuse(uid, documentEntity, req.body.refused_reason);
//create email for asked document
// this.emailBuilder.sendDocumentEmails(documentEntityUpdated);

View File

@ -2,7 +2,7 @@ import { Response, Request } from "express";
import { Controller, Get, Post } from "@ControllerPattern/index";
import ApiController from "@Common/system/controller-pattern/ApiController";
import { Service } from "typedi";
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import { Document, OfficeFolder } from "le-coffre-resources/dist/Notary";
import { getFolderHashes } from "@Common/optics/notary";
import OfficeFoldersService from "@Services/notary/OfficeFoldersService/OfficeFoldersService";
import OfficeFolderAnchorsRepository from "@Repositories/OfficeFolderAnchorsRepository";
@ -136,6 +136,18 @@ export default class OfficeFoldersController extends ApiController {
const officeFolder = OfficeFolder.hydrate<OfficeFolder>(officeFolderFound, { strategy: "excludeAll" });
// Check if every document is validated in a folder
const documents = officeFolder.documents ?? [];
const documentsValidated = documents.filter((document) => {
let documentHydrated = Document.hydrate<Document>(document, { strategy: "excludeAll" });
return documentHydrated.document_status === "VALIDATED";
});
if (documentsValidated.length !== documents.length && documents.length !== 0) {
this.httpBadRequest(response, "Cannot anchor a folder with non validated documents");
return;
}
const folderHashes = getFolderHashes(officeFolder);
if (folderHashes.length === 0) {

View File

@ -44,9 +44,9 @@ export default class OfficeFoldersController extends ApiController {
customers: {
some: {
OR: [
{contact: { first_name: { contains: filter, mode: "insensitive" } }},
{contact: { last_name: { contains: filter, mode: "insensitive" } }},
]
{ contact: { first_name: { contains: filter, mode: "insensitive" } } },
{ contact: { last_name: { contains: filter, mode: "insensitive" } } },
],
},
},
},
@ -56,8 +56,8 @@ export default class OfficeFoldersController extends ApiController {
}
const userId: string = req.body.user.userId;
if(query.where?.stakeholders) delete query.where.stakeholders;
const officeFoldersWhereInput: Prisma.OfficeFoldersWhereInput = { ...query.where, stakeholders: {some: {uid: userId }}};
if (query.where?.stakeholders) delete query.where.stakeholders;
const officeFoldersWhereInput: Prisma.OfficeFoldersWhereInput = { ...query.where, stakeholders: { some: { uid: userId } } };
query.where = officeFoldersWhereInput;
//call service to get prisma entity
@ -111,7 +111,9 @@ export default class OfficeFoldersController extends ApiController {
return;
}
const officeFolderFound = await this.officeFoldersService.getByUid(uid);
const officeFolderFound = await this.officeFoldersService.getByUid(uid, {
folder_anchor: true,
});
if (!officeFolderFound) {
this.httpNotFoundRequest(response, "office folder not found");
@ -119,13 +121,17 @@ export default class OfficeFoldersController extends ApiController {
}
//init OfficeFolder resource with request body values
const officeFolderEntity = OfficeFolder.hydrate<OfficeFolder>(req.body);
const officefolderToUpdate = OfficeFolder.hydrate<OfficeFolder>(req.body);
const officeFolderFoundEntity = OfficeFolder.hydrate<OfficeFolder>(officeFolderFound);
//validate folder
await validateOrReject(officeFolderEntity, { groups: ["updateFolder"], forbidUnknownValues: false });
await validateOrReject(officefolderToUpdate, { groups: ["updateFolder"], forbidUnknownValues: false });
if (officeFolderFoundEntity.folder_anchor?.status === "VERIFIED_ON_CHAIN") {
this.httpBadRequest(response, "Cannot update a verified folder");
return;
}
//call service to get prisma entity
const officeFolderEntityUpdated = await this.officeFoldersService.update(uid, officeFolderEntity);
const officeFolderEntityUpdated = await this.officeFoldersService.update(uid, officefolderToUpdate);
//Hydrate ressource with prisma entity
const officeFolders = OfficeFolder.hydrate<OfficeFolder>(officeFolderEntityUpdated, {
@ -140,6 +146,102 @@ export default class OfficeFoldersController extends ApiController {
}
}
/**
* @description Modify a specific folder by uid
*/
@Put("/api/v1/notary/folders/:uid/archive", [authHandler, ruleHandler, folderHandler])
protected async archive(req: Request, response: Response) {
try {
const uid = req.params["uid"];
if (!uid) {
this.httpBadRequest(response, "No uid provided");
return;
}
const officeFolderFound = await this.officeFoldersService.getByUid(uid, {
folder_anchor: true,
});
if (!officeFolderFound) {
this.httpNotFoundRequest(response, "office folder not found");
return;
}
//init OfficeFolder resource with request body values
const officefolderToUpdate = OfficeFolder.hydrate<OfficeFolder>(officeFolderFound);
officefolderToUpdate.status = "ARCHIVED";
officefolderToUpdate.archived_description = req.body.archived_description ?? "";
//validate folder
await validateOrReject(officefolderToUpdate, { groups: ["updateFolder"], forbidUnknownValues: false });
if ((officeFolderFound as any).folder_anchor?.status !== "VERIFIED_ON_CHAIN") {
this.httpBadRequest(response, "Cannot archive a not anchored folder");
return;
}
//call service to get prisma entity
const officeFolderEntityUpdated = await this.officeFoldersService.update(uid, officefolderToUpdate);
//Hydrate ressource with prisma entity
const officeFolders = OfficeFolder.hydrate<OfficeFolder>(officeFolderEntityUpdated, {
strategy: "excludeAll",
});
//success
this.httpSuccess(response, officeFolders);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
/**
* @description Modify a specific folder by uid
*/
@Put("/api/v1/notary/folders/:uid/restore", [authHandler, ruleHandler, folderHandler])
protected async restore(req: Request, response: Response) {
try {
const uid = req.params["uid"];
if (!uid) {
this.httpBadRequest(response, "No uid provided");
return;
}
const officeFolderFound = await this.officeFoldersService.getByUid(uid, {
folder_anchor: true,
});
if (!officeFolderFound) {
this.httpNotFoundRequest(response, "office folder not found");
return;
}
//init OfficeFolder resource with request body values
const officefolderToUpdate = OfficeFolder.hydrate<OfficeFolder>(officeFolderFound);
officefolderToUpdate.status = "LIVE";
officefolderToUpdate.archived_description = "";
//validate folder
await validateOrReject(officefolderToUpdate, { groups: ["updateFolder"], forbidUnknownValues: false });
//call service to get prisma entity
const officeFolderEntityUpdated = await this.officeFoldersService.update(uid, officefolderToUpdate);
//Hydrate ressource with prisma entity
const officeFolders = OfficeFolder.hydrate<OfficeFolder>(officeFolderEntityUpdated, {
strategy: "excludeAll",
});
//success
this.httpSuccess(response, officeFolders);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
/**
* @description Get a specific folder by uid
* @returns IFolder

View File

@ -9,6 +9,7 @@ import authHandler from "@App/middlewares/AuthHandler";
import ruleHandler from "@App/middlewares/RulesHandler";
import roleHandler from "@App/middlewares/RolesHandler";
import { Prisma } from "@prisma/client";
import customerHandler from "@App/middlewares/OfficeMembershipHandlers/CustomerHandler";
@Controller()
@Service()
@ -78,7 +79,7 @@ export default class CustomersController extends ApiController {
/**
* @description Modify a specific customer by uid
*/
@Put("/api/v1/super-admin/customers/:uid", [authHandler, roleHandler, ruleHandler])
@Put("/api/v1/super-admin/customers/:uid", [authHandler, roleHandler, ruleHandler, customerHandler])
protected async put(req: Request, response: Response) {
try {
const uid = req.params["uid"];
@ -119,7 +120,7 @@ export default class CustomersController extends ApiController {
/**
* @description Get a specific customer by uid
*/
@Get("/api/v1/super-admin/customers/:uid", [authHandler, roleHandler, ruleHandler])
@Get("/api/v1/super-admin/customers/:uid", [authHandler, roleHandler, ruleHandler, customerHandler])
protected async getOneByUid(req: Request, response: Response) {
try {
const uid = req.params["uid"];

View File

@ -4,7 +4,7 @@ import roleHandler from "@App/middlewares/RolesHandler";
import ruleHandler from "@App/middlewares/RulesHandler";
import ApiController from "@Common/system/controller-pattern/ApiController";
import { Controller, Delete, Get, Post, Put } from "@ControllerPattern/index";
import { Documents, Prisma } from "@prisma/client";
import { Documents, EDocumentStatus, Prisma } from "@prisma/client";
import DocumentsService from "@Services/super-admin/DocumentsService/DocumentsService";
import { validateOrReject } from "class-validator";
import { Request, Response } from "express";
@ -32,9 +32,9 @@ export default class DocumentsController extends ApiController {
}
const officeId: string = req.body.user.office_Id;
const officeWhereInput: Prisma.OfficesWhereInput = { uid: officeId } ;
const officeWhereInput: Prisma.OfficesWhereInput = { uid: officeId };
if(!query.where) query.where = { document_type : {office: officeWhereInput}};
if (!query.where) query.where = { document_type: { office: officeWhereInput } };
// query.where.document_type!.office = officeWhereInput;
@ -108,7 +108,55 @@ export default class DocumentsController extends ApiController {
await validateOrReject(documentEntity, { groups: ["updateDocument"] });
//call service to get prisma entity
const documentEntityUpdated: Documents = await this.documentsService.update(uid, documentEntity, req.body.refused_reason);
const documentEntityUpdated: Documents = await this.documentsService.update(uid, documentEntity);
//Hydrate ressource with prisma entity
const document = Document.hydrate<Document>(documentEntityUpdated, { strategy: "excludeAll" });
//success
this.httpSuccess(response, document);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
/**
* @description Update a specific document
*/
@Put("/api/v1/notary/documents/:uid/refuse", [authHandler, ruleHandler, documentHandler])
protected async refuseDocument(req: Request, response: Response) {
try {
const uid = req.params["uid"];
if (!uid) {
this.httpBadRequest(response, "No uid provided");
return;
}
const documentFound = await this.documentsService.getByUid(uid, {
files: true,
});
if (!documentFound) {
this.httpNotFoundRequest(response, "document not found");
return;
}
//init Document resource with request body values
const documentEntity = Document.hydrate<Document>(documentFound);
// Status to refuse
documentEntity.document_status = EDocumentStatus.REFUSED;
//validate document
await validateOrReject(documentEntity, { groups: ["updateDocument"] });
//call service to get prisma entity
const documentEntityUpdated: Documents = await this.documentsService.refuse(uid, documentEntity, req.body.refused_reason);
//create email for asked document
// this.emailBuilder.sendDocumentEmails(documentEntityUpdated);
// this.notificationBuilder.sendDocumentAnchoredNotificatiom(documentEntityUpdated);
//Hydrate ressource with prisma entity
const document = Document.hydrate<Document>(documentEntityUpdated, { strategy: "excludeAll" });

View File

@ -1,6 +1,5 @@
import authHandler from "@App/middlewares/AuthHandler";
import roleHandler from "@App/middlewares/RolesHandler";
import NotificationBuilder from "@Common/notifications/NotificationBuilder";
import ApiController from "@Common/system/controller-pattern/ApiController";
import { Controller, Delete, Post } from "@ControllerPattern/index";
import { EAppointmentStatus, Votes } from "@prisma/client";
@ -17,7 +16,6 @@ export default class LiveVoteController extends ApiController {
constructor(
private liveVoteService: LiveVoteService,
private usersService: UsersService,
private notificationBuilder: NotificationBuilder,
) {
super();
}
@ -30,7 +28,7 @@ export default class LiveVoteController extends ApiController {
try {
const userId = req.body.user.userId;
//init IUser resource with request body values
const voteEntity = Vote.hydrate<Vote>(req.body);
const voteEntity = Vote.hydrate<Vote>(req.body, { strategy: "excludeAll" });
//validate user
await validateOrReject(voteEntity, { groups: ["createVote"] });
@ -54,7 +52,7 @@ export default class LiveVoteController extends ApiController {
AND: [
{
appointment: {
AND: [{ user_uid: voteEntity.appointment.targeted_user.uid }, { status: EAppointmentStatus.OPEN }],
AND: [{ user_uid: voteEntity.appointment.user.uid }, { status: EAppointmentStatus.OPEN }],
},
},
{ voter: { uid: userId } },
@ -79,11 +77,7 @@ export default class LiveVoteController extends ApiController {
return;
}
//Hydrate ressource with prisma entity
const vote = Vote.hydrate<Vote>(voteEntityCreated, {
strategy: "excludeAll",
});
await this.notificationBuilder.sendVoteNotification(vote);
const vote = Vote.hydrate<Vote>(voteEntityCreated, { strategy: "excludeAll" });
//success
this.httpCreated(response, vote);

View File

@ -44,23 +44,22 @@ export default class OfficeFoldersController extends ApiController {
{
customers: {
some: {
contact: {
OR: [
{ first_name: { contains: filter, mode: "insensitive" } },
{ last_name: { contains: filter, mode: "insensitive" } },
{ contact: { first_name: { contains: filter, mode: "insensitive" } } },
{ contact: { last_name: { contains: filter, mode: "insensitive" } } },
],
},
},
},
},
],
},
};
}
const officeId: string = req.body.user.office_Id;
const officeWhereInput: Prisma.OfficesWhereInput = { uid: officeId };
if (!query.where) query.where = { office: officeWhereInput };
query.where.office = officeWhereInput;
const userId: string = req.body.user.userId;
if (query.where?.stakeholders) delete query.where.stakeholders;
const officeFoldersWhereInput: Prisma.OfficeFoldersWhereInput = { ...query.where, stakeholders: { some: { uid: userId } } };
query.where = officeFoldersWhereInput;
//call service to get prisma entity
const officeFolderEntities: OfficeFolders[] = await this.officeFoldersService.get(query);

View File

@ -109,24 +109,25 @@ export default class UsersController extends ApiController {
//init IUser resource with request body values
const userEntity = User.hydrate<User>(req.body);
if(userEntity.role) {
if (userEntity.role) {
const role = await this.roleService.getByUid(userEntity.role.uid!);
if(!role) {
if (!role) {
this.httpBadRequest(response, "Role not found");
return;
}
if (role.name === "super-admin" || userFound.role.name === "super-admin" ) {
if (role.name === "super-admin" || userFound.role.name === "super-admin") {
this.httpBadRequest(response, "Cannot assign or remove super-admin role");
return;
}
}
if(userEntity.office_role) {
if (userEntity.office_role) {
const officeRole = await this.officeRoleService.getByUid(userEntity.office_role.uid!);
if(!officeRole) {
if (!officeRole) {
this.httpBadRequest(response, "Office role not found");
return;
}
if (officeRole.office_uid != userFound.office_uid) {
this.httpBadRequest(response, "Cannot assign an office role from another office");
return;

View File

@ -1,5 +1,6 @@
import HttpCodes from "@Common/system/controller-pattern/HttpCodes";
import DocumentsService from "@Services/customer/DocumentsService/DocumentsService";
import Document from "le-coffre-resources/dist/SuperAdmin/Document";
import { NextFunction, Request, Response } from "express";
import Container from "typedi";
@ -14,15 +15,27 @@ export default async function documentHandler(req: Request, response: Response,
}
const documentService = Container.get(DocumentsService);
const document = await documentService.getByUid(uid);
const document = await documentService.getByUid(uid, { folder: { include: { folder_anchor: true } } });
if (!document) {
response.status(HttpCodes.NOT_FOUND).send("Document not found");
return;
}
if (document?.depositor_uid != customerId) {
response.status(HttpCodes.UNAUTHORIZED).send("Not authorized with this depositor");
return;
}
next();
if (req.method === "POST" || req.method === "PUT") {
const documentEntity = Document.hydrate<Document>(document);
if (documentEntity.folder?.folder_anchor?.status === "VERIFIED_ON_CHAIN") {
response.status(HttpCodes.BAD_REQUEST).send("Cannot update a verified folder");
return;
}
}
next();
} catch (error) {
console.log(error);
response.status(HttpCodes.INTERNAL_ERROR).send("Internal server error");

View File

@ -1,13 +1,20 @@
import HttpCodes from "@Common/system/controller-pattern/HttpCodes";
import FilesService from "@Services/common/FilesService/FilesService";
import DocumentsService from "@Services/customer/DocumentsService/DocumentsService";
import File from "le-coffre-resources/dist/SuperAdmin/File";
import { NextFunction, Request, Response } from "express";
import Container from "typedi";
import { EDocumentStatus } from "@prisma/client";
export default async function fileHandler(req: Request, response: Response, next: NextFunction) {
const customerId = req.body.user.customerId;
const uid = req.path && req.path.split("/")[5];
const document = req.body.document;
const file: string | undefined = req.body["q"];
if (req.file && req.file.mimetype !== "application/pdf" && req.file.mimetype !== "image/png" && req.file.mimetype !== "image/jpeg") {
response.status(HttpCodes.BAD_REQUEST).send("File type not supported");
return;
}
if (uid) {
const fileService = Container.get(FilesService);
@ -20,12 +27,18 @@ export default async function fileHandler(req: Request, response: Response, next
response.status(HttpCodes.UNAUTHORIZED).send("Not authorized with this depositor");
return;
}
if (req.method === "PUT") {
if (file.document.document_status === EDocumentStatus.VALIDATED) {
response.status(HttpCodes.BAD_REQUEST).send("Cannot update a validated document");
return;
}
if (document) {
}
}
if (file) {
const fileEntity = File.hydrate<File>(JSON.parse(file));
const documentService = Container.get(DocumentsService);
const documentFound = await documentService.getByUid(document.uid!);
if(!documentFound) {
const documentFound = await documentService.getByUid(fileEntity.document?.uid!, { folder: { include: { folder_anchor: true } } });
if (!documentFound) {
response.status(HttpCodes.NOT_FOUND).send("Document not found");
return;
}
@ -33,6 +46,10 @@ export default async function fileHandler(req: Request, response: Response, next
response.status(HttpCodes.UNAUTHORIZED).send("Not authorized with this depositor");
return;
}
if (documentFound.document_status === EDocumentStatus.VALIDATED) {
response.status(HttpCodes.BAD_REQUEST).send("Cannot update a validated document");
return;
}
}
next();

View File

@ -0,0 +1,28 @@
import HttpCodes from "@Common/system/controller-pattern/HttpCodes";
import { NextFunction, Request, Response } from "express";
import Container from "typedi";
import CustomersService from "@Services/super-admin/CustomersService/CustomersService";
export default async function customerHandler(req: Request, response: Response, next: NextFunction) {
try {
const officeId = req.body.user.office_Id;
const uid = req.path && req.path.split("/")[5];
if (uid) {
const customerService = Container.get(CustomersService);
const customer = await customerService.get({where:{AND: [{uid: uid}, {office_folders: {some: {office_uid: officeId}}}]}});
if (!customer[0]) {
response.status(HttpCodes.NOT_FOUND).send("Customer not found");
return;
}
}
next();
} catch (error) {
console.log(error);
response.status(HttpCodes.INTERNAL_ERROR).send("Internal server error");
return;
}
}

View File

@ -17,6 +17,11 @@ export default class EmailBuilder {
const documentPrisma = await this.documentsService.getByUid(documentEntity.uid, { depositor: {include: {contact: true}}, folder:{include:{ office: true}} });
if(!documentPrisma) throw new Error("Document not found");
const document = Document.hydrate<Document>(documentPrisma);
//Use mailchimpService.get get if an email was sent to the user in the lst hour
const lastEmail = await this.mailchimpService.get({ where: { to: document.depositor!.contact!.email, sentAt: { gte: new Date(Date.now() - 3600000) } } });
if(lastEmail.length > 0) return;
const to = document.depositor!.contact!.email;
const civility = this.getCivility(document.depositor!.contact!.civility);
const templateVariables = {

View File

@ -14,7 +14,7 @@ export default class NotificationBuilder {
private documentsService: DocumentsService,
private usersService: UsersService,
private foldersService: OfficeFoldersService,
private backendVariables: BackendVariables
private backendVariables: BackendVariables,
) {}
public async sendDocumentDepositedNotification(documentEntity: Documents) {
@ -42,10 +42,12 @@ export default class NotificationBuilder {
}
public async sendFolderAnchoredNotification(folderEntity: OfficeFolders) {
if(!folderEntity.uid) return;
const officeFolderPrisma = await this.foldersService.getByUid(folderEntity.uid,
{ folder_anchor: true, office: true, stakeholders: true }
);
if (!folderEntity.uid) return;
const officeFolderPrisma = await this.foldersService.getByUid(folderEntity.uid, {
folder_anchor: true,
office: true,
stakeholders: true,
});
if (!officeFolderPrisma) throw new Error("Folder not found");
const folder = OfficeFolder.hydrate<OfficeFolder>(officeFolderPrisma);
if (folder.folder_anchor?.status !== "VERIFIED_ON_CHAIN") return;
@ -66,26 +68,24 @@ export default class NotificationBuilder {
public async sendVoteNotification(vote: Vote) {
if (vote.appointment.status !== "OPEN") return;
const superAdminList = await this.usersService.get({ where: { role: { label: "super-admin" } } });
const superAdminList = await this.usersService.get({ where: { role: { name: "super-admin" } } });
const voterIndex = superAdminList.findIndex((user) => user.uid === vote.voter.uid);
superAdminList.splice(voterIndex, 1);
const userTargeted = await this.usersService.getByUid(vote.appointment.user.uid!, { contact: true });
const userTargetedEntity = User.hydrate<User>(userTargeted!, { strategy: "excludeAll" });
let message = "";
if (vote.appointment.choice === "NOMINATE") {
message =
"Un collaborateur souhaite attribuer le titre de Super Administrateur à " +
vote.appointment.targeted_user +
". Cliquez ici pour voter.";
message = `Un collaborateur souhaite attribuer le titre de Super Administrateur à ${userTargetedEntity.contact?.first_name} ${userTargetedEntity.contact?.last_name}. Cliquez ici pour voter.`;
} else if (vote.appointment.choice === "DISMISS") {
message =
"Un collaborateur souhaite retirer le titre de Super Administrateur à " +
vote.appointment.targeted_user +
". Cliquez ici pour voter.";
message = `Un collaborateur souhaite retirer le titre de Super Administrateur à ${userTargetedEntity.contact?.first_name} ${userTargetedEntity.contact?.last_name}. Cliquez ici pour voter.`;
}
this.notificationsService.create({
message: message,
redirection_url: `${this.backendVariables.APP_HOST}/users/${vote.appointment.targeted_user.uid}`,
redirection_url: `${this.backendVariables.APP_HOST}/users/${vote.appointment.user.uid}`,
created_at: new Date(),
updated_at: new Date(),
user: superAdminList || [],
user: superAdminList,
});
}

View File

@ -1,7 +1,7 @@
import Database from "@Common/databases/database";
import BaseRepository from "@Repositories/BaseRepository";
import { Service } from "typedi";
import { Appointments, EAppointmentStatus, Prisma } from "@prisma/client";
import { Appointments, EAppointmentStatus, EVote, Prisma, Users, Votes } from "@prisma/client";
@Service()
export default class AppointmentsRepository extends BaseRepository {
@ -39,6 +39,18 @@ export default class AppointmentsRepository extends BaseRepository {
return this.model.update(updateArgs);
}
public async findOneByStatusUserAndChoice(userUid: string, choice: EVote, status: EAppointmentStatus): Promise<Appointments | null> {
return this.model.findUnique({
where: {
user_uid_choice_status: {
user_uid: userUid,
choice: choice,
status: status,
},
},
});
}
/**
* @description : Find one appointment
*/
@ -54,12 +66,12 @@ export default class AppointmentsRepository extends BaseRepository {
/**
* @description : Find one appointment with votes
*/
public async findOneByUidWithVotes(uid: string) {
public async findOneByUidWithVotes(uid: string): Promise<(Appointments & {votes: Votes[], user: Users}) | null> {
return this.model.findUnique({
where: {
uid: uid,
},
include: {votes: true},
include: {votes: true, user: true},
});
}

View File

@ -21,7 +21,6 @@ export default class DocumentsRepository extends BaseRepository {
* @description : Find many documents
*/
public async findMany(query: Prisma.DocumentsFindManyArgs) {
query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows);
return this.model.findMany(query);
}
@ -96,7 +95,26 @@ export default class DocumentsRepository extends BaseRepository {
/**
* @description : Update data of a document
*/
public async update(uid: string, document: Partial<DocumentCustomer>, refusedReason?: string): Promise<Documents> {
public async update(uid: string, document: Partial<DocumentCustomer>): Promise<Documents> {
return this.model.update({
where: {
uid: uid,
},
data: {
document_status: EDocumentStatus[document.document_status as keyof typeof EDocumentStatus],
document_history: {
create: {
document_status: EDocumentStatus[document.document_status as keyof typeof EDocumentStatus],
},
},
},
});
}
/**
* @description : Update data of a document
*/
public async refuse(uid: string, document: Partial<DocumentCustomer>, refusedReason?: string): Promise<Documents> {
return this.model.update({
where: {
uid: uid,

View File

@ -29,10 +29,10 @@ export default class VotesRepository extends BaseRepository {
*/
public async create(vote: Vote): Promise<Votes> {
let whereArg: Prisma.AppointmentsWhereUniqueInput;
if(vote.appointment.targeted_user.uid) {
if(vote.appointment.user.uid) {
whereArg = {
user_uid_choice_status: {
user_uid: vote.appointment.targeted_user.uid,
user_uid: vote.appointment.user.uid,
choice: EVote[vote.appointment.choice as keyof typeof EVote],
status: EAppointmentStatus.OPEN,
}
@ -49,7 +49,7 @@ export default class VotesRepository extends BaseRepository {
where: whereArg,
create: {
choice: EVote[vote.appointment.choice as keyof typeof EVote],
user_uid: vote.appointment.targeted_user.uid!,
user_uid: vote.appointment.user.uid!,
}
},
},
@ -61,7 +61,7 @@ export default class VotesRepository extends BaseRepository {
}
};
return this.model.create({...createArgs, include: {appointment: {include: {votes: true}}}});
return this.model.create({...createArgs, include: {voter: true, appointment: {include: {votes: true, user: true}}}});
}
/**

View File

@ -25,8 +25,8 @@ const storage = multer.memoryStorage();
middlwares: [
cors({ origin: "*" }),
multer({ storage: storage, limits: { fileSize: 32000000 } }).single("file"), //32 MB maximum
bodyParser.urlencoded({ extended: true }),
bodyParser.json(),
bodyParser.json({ limit: "35mb"}),
bodyParser.urlencoded({ extended: true, limit: "35mb", parameterLimit: 50000 }),
],
errorHandler,
});

View File

@ -3,10 +3,11 @@ import { Document } from "le-coffre-resources/dist/Admin";
import DocumentsRepository from "@Repositories/DocumentsRepository";
import BaseService from "@Services/BaseService";
import { Service } from "typedi";
import FilesRepository from "@Repositories/FilesRepository";
@Service()
export default class DocumentsService extends BaseService {
constructor(private documentsRepository: DocumentsRepository) {
constructor(private documentsRepository: DocumentsRepository, private filesRepository: FilesRepository) {
super();
}
@ -38,8 +39,19 @@ export default class DocumentsService extends BaseService {
* @description : Modify a document
* @throws {Error} If document cannot be modified
*/
public async update(uid: string, document: Partial<Document>, refused_reason?: string): Promise<Documents> {
return this.documentsRepository.update(uid, document, refused_reason);
public async update(uid: string, document: Partial<Document>): Promise<Documents> {
return this.documentsRepository.update(uid, document);
}
public async refuse(uid: string, document: Partial<Document>, refused_reason: string): Promise<Documents> {
if (document.files) {
for (let i = 0; i < document.files.length; i++) {
console.log("archiving file", document.files[i]?.uid);
await this.filesRepository.deleteKeyAndArchive(document.files[i]?.uid as string);
}
}
return this.documentsRepository.refuse(uid, document, refused_reason);
}
/**

View File

@ -72,8 +72,7 @@ export default class AnchoringProofService extends BaseService {
*/
public async generate(data: AnchoringProofData): Promise<Buffer> {
const browser = await puppeteer.launch({
headless: "new",
executablePath: process.env['PUPPETEER_EXECUTABLE_PATH'],
headless: 'new',
args: ["--no-sandbox", "--disable-setuid-sandbox"],
});
const page = await browser.newPage();

View File

@ -4,7 +4,7 @@ import { BackendVariables } from "@Common/config/variables/Variables";
import DocumentsService from "@Services/super-admin/DocumentsService/DocumentsService";
import FilesService from "../FilesService/FilesService";
type EnrollmentResponse = {
export type EnrollmentResponse = {
url: string;
id: number;
api_key: string;
@ -196,13 +196,12 @@ export default class Id360Service extends BaseService {
}
public async getEnrollment(token: string) {
const res = await fetch(
return await fetch(
`${
this.variables.DOCAPOST_BASE_URL + this.variables.DOCAPOST_ROOT + this.variables.DOCAPOST_VERSION
}/enrollment/status/${token}`,
{ method: "GET" },
);
return (await res.json()) as EnrollmentResponse;
}
public async finalizeEnrollment(apiKey: string) {

View File

@ -1,6 +1,6 @@
import EmailRepository from "@Repositories/EmailRepository";
import BaseService from "@Services/BaseService";
import { Emails } from "@prisma/client";
import { Emails, Prisma } from "@prisma/client";
import { Service } from "typedi";
import MailchimpClient from "@mailchimp/mailchimp_transactional";
import { BackendVariables } from "@Common/config/variables/Variables";
@ -24,7 +24,7 @@ export default class MailchimpService extends BaseService {
* @description : Get all emails
* @throws {Error} If emails cannot be get
*/
public async get(query: any): Promise<Emails[]> {
public async get(query: Prisma.EmailsFindManyArgs): Promise<Emails[]> {
return this.emailRepository.findMany(query);
}

View File

@ -3,10 +3,11 @@ import { Document } from "le-coffre-resources/dist/Notary";
import DocumentsRepository from "@Repositories/DocumentsRepository";
import BaseService from "@Services/BaseService";
import { Service } from "typedi";
import FilesRepository from "@Repositories/FilesRepository";
@Service()
export default class DocumentsService extends BaseService {
constructor(private documentsRepository: DocumentsRepository) {
constructor(private documentsRepository: DocumentsRepository, private filesRepository: FilesRepository) {
super();
}
@ -38,8 +39,19 @@ export default class DocumentsService extends BaseService {
* @description : Modify a document
* @throws {Error} If document cannot be modified
*/
public async update(uid: string, document: Partial<Document>, refused_reason?: string): Promise<Documents> {
return this.documentsRepository.update(uid, document, refused_reason);
public async update(uid: string, document: Partial<Document>): Promise<Documents> {
return this.documentsRepository.update(uid, document);
}
public async refuse(uid: string, document: Partial<Document>, refused_reason: string): Promise<Documents> {
if (document.files) {
for (let i = 0; i < document.files.length; i++) {
console.log("archiving file", document.files[i]?.uid);
await this.filesRepository.deleteKeyAndArchive(document.files[i]?.uid as string);
}
}
return this.documentsRepository.refuse(uid, document, refused_reason);
}
/**

View File

@ -3,10 +3,11 @@ import { Document } from "le-coffre-resources/dist/SuperAdmin";
import DocumentsRepository from "@Repositories/DocumentsRepository";
import BaseService from "@Services/BaseService";
import { Service } from "typedi";
import FilesRepository from "@Repositories/FilesRepository";
@Service()
export default class DocumentsService extends BaseService {
constructor(private documentsRepository: DocumentsRepository) {
constructor(private documentsRepository: DocumentsRepository, private filesRepository: FilesRepository) {
super();
}
@ -38,8 +39,19 @@ export default class DocumentsService extends BaseService {
* @description : Modify a document
* @throws {Error} If document cannot be modified
*/
public async update(uid: string, document: Partial<Document>, refused_reason?: string): Promise<Documents> {
return this.documentsRepository.update(uid, document, refused_reason);
public async update(uid: string, document: Partial<Document>): Promise<Documents> {
return this.documentsRepository.update(uid, document);
}
public async refuse(uid: string, document: Partial<Document>, refused_reason: string): Promise<Documents> {
if (document.files) {
for (let i = 0; i < document.files.length; i++) {
console.log("archiving file", document.files[i]?.uid);
await this.filesRepository.deleteKeyAndArchive(document.files[i]?.uid as string);
}
}
return this.documentsRepository.refuse(uid, document, refused_reason);
}
/**
@ -69,7 +81,7 @@ export default class DocumentsService extends BaseService {
* @description : Get a document by uid
* @throws {Error} If document cannot be get by uid
*/
public async getByUidWithFiles(uid: string): Promise<Documents & {files: Files[] | null} | null> {
public async getByUidWithFiles(uid: string): Promise<(Documents & { files: Files[] | null }) | null> {
return this.documentsRepository.findOneByUidWithFiles(uid);
}

View File

@ -2,10 +2,11 @@ import BaseService from "@Services/BaseService";
import { Service } from "typedi";
import User, { Appointment, Role, Vote } from "le-coffre-resources/dist/SuperAdmin";
import VotesRepository from "@Repositories/VotesRepository";
import { Appointments, EAppointmentStatus, EVote, Prisma, Votes } from "@prisma/client";
import { Appointments, EAppointmentStatus, EVote, Prisma, Users, Votes } from "@prisma/client";
import UsersService from "../UsersService/UsersService";
import RolesService from "../RolesService/RolesService";
import AppointmentsRepository from "@Repositories/AppointmentsRepository";
import NotificationBuilder from "@Common/notifications/NotificationBuilder";
@Service()
export default class LiveVoteService extends BaseService {
@ -14,12 +15,13 @@ export default class LiveVoteService extends BaseService {
private appointmentRepository: AppointmentsRepository,
private userService: UsersService,
private roleService: RolesService,
private notificationBuilder: NotificationBuilder,
) {
super();
}
public async verifyVoterChoice(vote: Vote): Promise<boolean> {
const userWithRole = await this.userService.getByUidWithRole(vote.appointment.targeted_user.uid!);
const userWithRole = await this.userService.getByUidWithRole(vote.appointment.user.uid!);
if (userWithRole!.role.name === "super-admin" && vote.appointment.choice === EVote.DISMISS) {
return true;
}
@ -65,27 +67,35 @@ export default class LiveVoteService extends BaseService {
return this.appointmentRepository.findOneByUid(uid, query);
}
public async getAppointmentWithVotes(vote: Vote): Promise<Appointments | null> {
public async getAppointmentWithVotes(vote: Vote): Promise<(Appointments & { votes: Votes[]; user: Users }) | null> {
if (vote.appointment.uid) {
return this.appointmentRepository.findOneByUid(vote.appointment.uid);
return this.appointmentRepository.findOneByUidWithVotes(vote.appointment.uid);
}
const appointmentByUser = await this.appointmentRepository.findMany({
where: {
AND: [
{ user_uid: vote.appointment.targeted_user.uid },
{ user_uid: vote.appointment.user.uid },
{ status: EAppointmentStatus.OPEN },
{ choice: EVote[vote.appointment.choice as keyof typeof EVote] },
],
},
include: { votes: true },
include: { votes: true, user: true },
});
if (appointmentByUser.length >= 1) {
if (appointmentByUser.length != 0) {
return this.appointmentRepository.findOneByUidWithVotes(appointmentByUser[0]!.uid);
}
return null;
}
private async closeVote(appointment: Appointments, vote: Votes) {
const apointmentFound = await this.appointmentRepository.findOneByStatusUserAndChoice(
appointment.user_uid,
EVote[appointment.choice as keyof typeof EVote],
EAppointmentStatus.CLOSED,
);
if (apointmentFound) {
await this.appointmentRepository.delete(apointmentFound.uid);
}
await this.appointmentRepository.update(vote.appointment_uid, EAppointmentStatus.CLOSED);
const user = await this.userService.getByUid(appointment.user_uid, { role: true });
const userEntity = User.hydrate<User>(user!, { strategy: "excludeAll" });
@ -98,12 +108,14 @@ export default class LiveVoteService extends BaseService {
const roles = await this.roleService.get({ where: { name: "default" } });
const roleEntity = Role.hydrate<Role>(roles[0]!, { strategy: "excludeAll" });
userEntity.role = roleEntity;
await this.notificationBuilder.sendDismissNotification(userEntity);
await this.userService.update(appointment!.user_uid, userEntity);
return vote;
} else if (appointment.choice === EVote.NOMINATE) {
const roles = await this.roleService.get({ where: { name: "super-admin" } });
const roleEntity = Role.hydrate<Role>(roles[0]!, { strategy: "excludeAll" });
userEntity!.role = roleEntity;
await this.notificationBuilder.sendNominateNotification(userEntity);
await this.userService.update(appointment!.user_uid, userEntity);
return vote;
}
@ -118,8 +130,10 @@ export default class LiveVoteService extends BaseService {
const appointment = await this.getAppointmentWithVotes(vote);
if (appointment) {
const appointmentEntity = Appointment.hydrate<Appointment>(appointment, { strategy: "excludeAll" });
if (appointmentEntity?.votes && appointmentEntity.votes.length >= 2) {
const voteEntity = Vote.hydrateArray<Vote>(appointment.votes, { strategy: "excludeAll" });
const appointementWithVotesHydrated = { ...appointment, votes: voteEntity };
const appointmentEntity = Appointment.hydrate<Appointment>(appointementWithVotesHydrated, { strategy: "excludeAll" });
if (appointmentEntity.votes && appointmentEntity.votes.length >= 2) {
const voteCreated = await this.voteRepository.create(vote);
return this.closeVote(appointment, voteCreated);
}
@ -127,7 +141,9 @@ export default class LiveVoteService extends BaseService {
const approvedChoice = await this.verifyVoterChoice(vote);
if (!approvedChoice) return null;
return this.voteRepository.create(vote);
const voteCreated = await this.voteRepository.create(vote);
const voteEntity = Vote.hydrate<Vote>(voteCreated, { strategy: "excludeAll" });
await this.notificationBuilder.sendVoteNotification(voteEntity);
return voteCreated;
}
}