refacto votes and notifications

This commit is contained in:
OxSaitama 2023-10-04 19:25:20 +02:00
parent 46cc08eeca
commit 1f339299bb
10 changed files with 67 additions and 44 deletions

View File

@ -53,7 +53,7 @@
"express": "^4.18.2", "express": "^4.18.2",
"fp-ts": "^2.16.1", "fp-ts": "^2.16.1",
"jsonwebtoken": "^9.0.0", "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", "module-alias": "^2.2.2",
"monocle-ts": "^2.3.13", "monocle-ts": "^2.3.13",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",

View File

@ -2,7 +2,7 @@ import { Response, Request } from "express";
import { Controller, Post } from "@ControllerPattern/index"; import { Controller, Post } from "@ControllerPattern/index";
import ApiController from "@Common/system/controller-pattern/ApiController"; import ApiController from "@Common/system/controller-pattern/ApiController";
import { Service } from "typedi"; 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 CustomersService from "@Services/customer/CustomersService/CustomersService";
import AuthService from "@Services/common/AuthService/AuthService"; import AuthService from "@Services/common/AuthService/AuthService";
import { Customer } from "le-coffre-resources/dist/SuperAdmin"; import { Customer } from "le-coffre-resources/dist/SuperAdmin";
@ -34,7 +34,11 @@ export default class CustomerController extends ApiController {
return; return;
} }
try { 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") { if (enrollment.status !== "OK") {
this.httpUnauthorized(response, "Enrollment status is not OK"); this.httpUnauthorized(response, "Enrollment status is not OK");
return; return;

View File

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

View File

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

View File

@ -30,7 +30,7 @@ export default class LiveVoteController extends ApiController {
try { try {
const userId = req.body.user.userId; const userId = req.body.user.userId;
//init IUser resource with request body values //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 //validate user
await validateOrReject(voteEntity, { groups: ["createVote"] }); await validateOrReject(voteEntity, { groups: ["createVote"] });
@ -54,7 +54,7 @@ export default class LiveVoteController extends ApiController {
AND: [ AND: [
{ {
appointment: { 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 } }, { voter: { uid: userId } },
@ -79,9 +79,7 @@ export default class LiveVoteController extends ApiController {
return; return;
} }
//Hydrate ressource with prisma entity //Hydrate ressource with prisma entity
const vote = Vote.hydrate<Vote>(voteEntityCreated, { const vote = Vote.hydrate<Vote>(voteEntityCreated, { strategy: "excludeAll" });
strategy: "excludeAll",
});
await this.notificationBuilder.sendVoteNotification(vote); await this.notificationBuilder.sendVoteNotification(vote);

View File

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

View File

@ -1,7 +1,7 @@
import Database from "@Common/databases/database"; import Database from "@Common/databases/database";
import BaseRepository from "@Repositories/BaseRepository"; import BaseRepository from "@Repositories/BaseRepository";
import { Service } from "typedi"; import { Service } from "typedi";
import { Appointments, EAppointmentStatus, Prisma } from "@prisma/client"; import { Appointments, EAppointmentStatus, EVote, Prisma, Users, Votes } from "@prisma/client";
@Service() @Service()
export default class AppointmentsRepository extends BaseRepository { export default class AppointmentsRepository extends BaseRepository {
@ -39,6 +39,18 @@ export default class AppointmentsRepository extends BaseRepository {
return this.model.update(updateArgs); 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 * @description : Find one appointment
*/ */
@ -54,12 +66,12 @@ export default class AppointmentsRepository extends BaseRepository {
/** /**
* @description : Find one appointment with votes * @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({ return this.model.findUnique({
where: { where: {
uid: uid, uid: uid,
}, },
include: {votes: true}, include: {votes: true, user: true},
}); });
} }

View File

@ -29,10 +29,10 @@ export default class VotesRepository extends BaseRepository {
*/ */
public async create(vote: Vote): Promise<Votes> { public async create(vote: Vote): Promise<Votes> {
let whereArg: Prisma.AppointmentsWhereUniqueInput; let whereArg: Prisma.AppointmentsWhereUniqueInput;
if(vote.appointment.targeted_user.uid) { if(vote.appointment.user.uid) {
whereArg = { whereArg = {
user_uid_choice_status: { 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], choice: EVote[vote.appointment.choice as keyof typeof EVote],
status: EAppointmentStatus.OPEN, status: EAppointmentStatus.OPEN,
} }
@ -49,7 +49,7 @@ export default class VotesRepository extends BaseRepository {
where: whereArg, where: whereArg,
create: { create: {
choice: EVote[vote.appointment.choice as keyof typeof EVote], 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}}}});
} }
/** /**

View File

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

View File

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