From 1f339299bbf7f9e366f1c0009314f7fd9e733211 Mon Sep 17 00:00:00 2001 From: OxSaitama Date: Wed, 4 Oct 2023 19:25:20 +0200 Subject: [PATCH] refacto votes and notifications --- package.json | 2 +- src/app/api/id360/CustomerController.ts | 8 ++++-- src/app/api/id360/DocumentController.ts | 2 -- src/app/api/idnot/UserController.ts | 4 +++ src/app/api/super-admin/LiveVoteController.ts | 8 ++---- .../notifications/NotificationBuilder.ts | 28 +++++++++---------- .../repositories/AppointmentsRepository.ts | 18 ++++++++++-- src/common/repositories/VotesRepository.ts | 8 +++--- .../common/Id360Service/Id360Service.ts | 5 ++-- .../LiveVoteService/LiveVoteService.ts | 28 +++++++++++++------ 10 files changed, 67 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index d64f06cf..c0ec46b8 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/api/id360/CustomerController.ts b/src/app/api/id360/CustomerController.ts index fc234983..475b5bb6 100644 --- a/src/app/api/id360/CustomerController.ts +++ b/src/app/api/id360/CustomerController.ts @@ -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; diff --git a/src/app/api/id360/DocumentController.ts b/src/app/api/id360/DocumentController.ts index 1b8d43ca..b0285212 100644 --- a/src/app/api/id360/DocumentController.ts +++ b/src/app/api/id360/DocumentController.ts @@ -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); diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 102af93a..9952e029 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -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) { diff --git a/src/app/api/super-admin/LiveVoteController.ts b/src/app/api/super-admin/LiveVoteController.ts index 65956b50..295ab1a7 100644 --- a/src/app/api/super-admin/LiveVoteController.ts +++ b/src/app/api/super-admin/LiveVoteController.ts @@ -30,7 +30,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(req.body); + const voteEntity = Vote.hydrate(req.body, { strategy: "excludeAll" }); //validate user await validateOrReject(voteEntity, { groups: ["createVote"] }); @@ -54,7 +54,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,9 +79,7 @@ export default class LiveVoteController extends ApiController { return; } //Hydrate ressource with prisma entity - const vote = Vote.hydrate(voteEntityCreated, { - strategy: "excludeAll", - }); + const vote = Vote.hydrate(voteEntityCreated, { strategy: "excludeAll" }); await this.notificationBuilder.sendVoteNotification(vote); diff --git a/src/common/notifications/NotificationBuilder.ts b/src/common/notifications/NotificationBuilder.ts index 3db177a2..30e88fc1 100644 --- a/src/common/notifications/NotificationBuilder.ts +++ b/src/common/notifications/NotificationBuilder.ts @@ -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(officeFolderPrisma); if (folder.folder_anchor?.status !== "VERIFIED_ON_CHAIN") return; @@ -66,23 +68,19 @@ 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 userTargeted = await this.usersService.getByUid(vote.appointment.user.uid!, { contact: true }); + const userTargetedEntity = User.hydrate(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 || [], diff --git a/src/common/repositories/AppointmentsRepository.ts b/src/common/repositories/AppointmentsRepository.ts index 62b74d34..dcb1e660 100644 --- a/src/common/repositories/AppointmentsRepository.ts +++ b/src/common/repositories/AppointmentsRepository.ts @@ -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 { + 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}, }); } diff --git a/src/common/repositories/VotesRepository.ts b/src/common/repositories/VotesRepository.ts index a9bf7746..d5f2bf55 100644 --- a/src/common/repositories/VotesRepository.ts +++ b/src/common/repositories/VotesRepository.ts @@ -29,10 +29,10 @@ export default class VotesRepository extends BaseRepository { */ public async create(vote: Vote): Promise { 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: {appointment: {include: {votes: true, user: true}}}}); } /** diff --git a/src/services/common/Id360Service/Id360Service.ts b/src/services/common/Id360Service/Id360Service.ts index 0271396d..9a98fe39 100644 --- a/src/services/common/Id360Service/Id360Service.ts +++ b/src/services/common/Id360Service/Id360Service.ts @@ -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) { diff --git a/src/services/super-admin/LiveVoteService/LiveVoteService.ts b/src/services/super-admin/LiveVoteService/LiveVoteService.ts index a034f976..65aaa726 100644 --- a/src/services/super-admin/LiveVoteService/LiveVoteService.ts +++ b/src/services/super-admin/LiveVoteService/LiveVoteService.ts @@ -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 { - 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,31 @@ export default class LiveVoteService extends BaseService { return this.appointmentRepository.findOneByUid(uid, query); } - public async getAppointmentWithVotes(vote: Vote): Promise { + 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!, { strategy: "excludeAll" }); @@ -98,12 +104,14 @@ export default class LiveVoteService extends BaseService { const roles = await this.roleService.get({ where: { name: "default" } }); const roleEntity = Role.hydrate(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(roles[0]!, { strategy: "excludeAll" }); userEntity!.role = roleEntity; + await this.notificationBuilder.sendNominateNotification(userEntity); await this.userService.update(appointment!.user_uid, userEntity); return vote; } @@ -118,8 +126,10 @@ export default class LiveVoteService extends BaseService { const appointment = await this.getAppointmentWithVotes(vote); if (appointment) { - const appointmentEntity = Appointment.hydrate(appointment, { strategy: "excludeAll" }); - if (appointmentEntity?.votes && appointmentEntity.votes.length >= 2) { + const voteEntity = Vote.hydrateArray(appointment.votes, { strategy: "excludeAll" }); + const appointementWithVotesHydrated = {...appointment, votes: voteEntity}; + const appointmentEntity = Appointment.hydrate(appointementWithVotesHydrated, { strategy: "excludeAll" }); + if (appointmentEntity.votes && appointmentEntity.votes.length >= 2) { const voteCreated = await this.voteRepository.create(vote); return this.closeVote(appointment, voteCreated); }