refacto vote creation in live-votes

This commit is contained in:
OxSaitama 2023-07-26 17:11:32 +02:00
parent d244158c4e
commit fffcd17944
11 changed files with 224 additions and 176 deletions

View File

@ -52,7 +52,7 @@
"cron": "^2.3.1",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.0",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.64",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.65",
"module-alias": "^2.2.2",
"multer": "^1.4.5-lts.1",
"next": "^13.1.5",

View File

@ -1,17 +1,15 @@
import { Response, Request } from "express";
import { Controller, Get, Post } from "@ControllerPattern/index";
import { Controller, Get } from "@ControllerPattern/index";
import ApiController from "@Common/system/controller-pattern/ApiController";
import AppointmentsService from "@Services/super-admin/AppointmentsService/AppointmentsService";
import { Service } from "typedi";
import { Appointment } from "le-coffre-resources/dist/SuperAdmin";
import { validateOrReject } from "class-validator";
import authHandler from "@App/middlewares/AuthHandler";
import UsersService from "@Services/super-admin/UsersService/UsersService";
@Controller()
@Service()
export default class AppointmentsController extends ApiController {
constructor(private appointmentsService: AppointmentsService, private usersService: UsersService) {
constructor(private appointmentsService: AppointmentsService) {
super();
}
@ -41,38 +39,6 @@ export default class AppointmentsController extends ApiController {
}
}
/**
* @description Create a new appointment
*/
@Post("/api/v1/super-admin/appointments", [authHandler])
protected async post(req: Request, response: Response) {
try {
//init IUser resource with request body values
const appointmentEntity = Appointment.hydrate<Appointment>(req.body);
//validate user
await validateOrReject(appointmentEntity, { groups: ["createAppointment"]});
const targetedUser = await this.usersService.getByUid(appointmentEntity.targeted_user.uid!);
if(!targetedUser) {
this.httpNotFoundRequest(response, "targeted user not found");
return;
}
//call service to get prisma entity
const appointmentEntityCreated = await this.appointmentsService.create(appointmentEntity);
//Hydrate ressource with prisma entity
const appointment = Appointment.hydrate<Appointment>(appointmentEntityCreated, {
strategy: "excludeAll",
});
//success
this.httpCreated(response, appointment);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
/**
* @description Get a specific appointment by uid
*/

View File

@ -0,0 +1,90 @@
import { Response, Request } from "express";
import { Controller, Post } from "@ControllerPattern/index";
import ApiController from "@Common/system/controller-pattern/ApiController";
import VotesService from "@Services/super-admin/VotesService/VotesService";
import { Service } from "typedi";
import { Vote } from "le-coffre-resources/dist/SuperAdmin";
import { validateOrReject } from "class-validator";
import authHandler from "@App/middlewares/AuthHandler";
import UsersService from "@Services/super-admin/UsersService/UsersService";
import { EAppointmentStatus } from "@prisma/client";
import AppointmentService from "@Services/super-admin/AppointmentsService/AppointmentsService";
import LiveVoteService from "@Services/super-admin/LiveVoteService/LiveVoteService";
@Controller()
@Service()
export default class LiveVoteController extends ApiController {
constructor(private liveVoteService: LiveVoteService, private votesService: VotesService, private usersService: UsersService, private appointmentService: AppointmentService) {
super();
}
/**
* @description Create a new vote
*/
@Post("/api/v1/super-admin/live-votes", [authHandler])
protected async post(req: Request, response: Response) {
try {
const userId = req.body.user.userId;
//init IUser resource with request body values
const voteEntity = Vote.hydrate<Vote>(req.body);
//validate user
await validateOrReject(voteEntity, { groups: ["createVote"] });
if (voteEntity.appointment.uid) {
const appointment = await this.appointmentService.getByUid(voteEntity.appointment.uid);
if (!appointment) {
this.httpNotFoundRequest(response, "Appointment not found");
return;
}
if (appointment.status === EAppointmentStatus.CLOSED) {
this.httpBadRequest(response, "Appointment is closed");
return;
}
}
const votes = await this.votesService.get({
where: {
OR: [
{ AND: [{ appointment: { uid: voteEntity.appointment.uid } }, { voter: { uid: userId } }] },
{
AND: [
{
appointment: {
AND: [{ user_uid: voteEntity.appointment.targeted_user.uid }, { status: EAppointmentStatus.OPEN }],
},
},
{ voter: { uid: userId } },
],
},
],
},
});
if (votes.length) {
this.httpBadRequest(response, "Voter already voted for this appointment");
return;
}
const voter = await this.usersService.getByUid(userId);
voteEntity.voter = voter!;
//call service to get prisma entity
const voteEntityCreated = await this.liveVoteService.create(voteEntity);
if(!voteEntityCreated) {
this.httpBadRequest(response, "Appointment choice is not valid");
return;
}
//Hydrate ressource with prisma entity
const vote = Vote.hydrate<Vote>(voteEntityCreated, {
strategy: "excludeAll",
});
//success
this.httpCreated(response, vote);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
}

View File

@ -1,19 +1,16 @@
import { Response, Request } from "express";
import { Controller, Delete, Get, Post } from "@ControllerPattern/index";
import { Controller, Delete, Get } from "@ControllerPattern/index";
import ApiController from "@Common/system/controller-pattern/ApiController";
import VotesService from "@Services/super-admin/VotesService/VotesService";
import { Service } from "typedi";
import { Vote } from "le-coffre-resources/dist/SuperAdmin";
import { validateOrReject } from "class-validator";
import authHandler from "@App/middlewares/AuthHandler";
import UsersService from "@Services/super-admin/UsersService/UsersService";
import { EAppointmentStatus, Votes } from "@prisma/client";
import AppointmentService from "@Services/super-admin/AppointmentsService/AppointmentsService";
import { Votes } from "@prisma/client";
@Controller()
@Service()
export default class VotesController extends ApiController {
constructor(private votesService: VotesService, private usersService: UsersService, private appointmentService: AppointmentService) {
constructor(private votesService: VotesService) {
super();
}
@ -43,55 +40,6 @@ export default class VotesController extends ApiController {
}
}
/**
* @description Create a new vote
*/
@Post("/api/v1/super-admin/votes", [authHandler])
protected async post(req: Request, response: Response) {
try {
const userId = req.body.user.userId;
//init IUser resource with request body values
const voteEntity = Vote.hydrate<Vote>(req.body);
//validate user
await validateOrReject(voteEntity, { groups: ["createVote"] });
const appointment = await this.appointmentService.getByUid(voteEntity.appointment.uid!);
if (!appointment) {
this.httpNotFoundRequest(response, "Appointment not found");
return;
}
if (appointment.status === EAppointmentStatus.CLOSED) {
this.httpBadRequest(response, "Appointment is closed");
return;
}
const votes = await this.votesService.get({
where: { AND: [{ appointment: { uid: voteEntity.appointment.uid } }, { voter: { uid: userId } }] },
});
console.log(votes);
if (votes.length) {
this.httpBadRequest(response, "Voter already voted for this appointment");
return;
}
const voter = await this.usersService.getByUid(userId);
voteEntity.voter = voter!;
//call service to get prisma entity
const voteEntityCreated = await this.votesService.create(voteEntity);
//Hydrate ressource with prisma entity
const vote = Vote.hydrate<Vote>(voteEntityCreated, {
strategy: "excludeAll",
});
//success
this.httpCreated(response, vote);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
/**
* @description Get a specific vote by uid
*/

View File

@ -42,6 +42,7 @@ import FilesControllerCustomer from "./api/customer/FilesController";
import DocumentsControllerCustomer from "./api/customer/DocumentsController";
import AppointmentsController from "./api/super-admin/AppointmentsController";
import VotesController from "./api/super-admin/VotesController";
import LiveVoteController from "./api/super-admin/LiveVoteController";
/**
@ -60,6 +61,7 @@ export default {
Container.get(DocumentTypesControllerSuperAdmin);
Container.get(AppointmentsController);
Container.get(VotesController);
Container.get(LiveVoteController);
Container.get(IdNotUserController);
Container.get(FranceConnectCustomerController);
Container.get(FilesControllerSuperAdmin);

View File

@ -1,8 +1,7 @@
import Database from "@Common/databases/database";
import BaseRepository from "@Repositories/BaseRepository";
import { Service } from "typedi";
import { Appointments, EAppointmentStatus, EVote, Prisma } from "@prisma/client";
import { Appointment } from "le-coffre-resources/dist/SuperAdmin";
import { Appointments, EAppointmentStatus, Prisma } from "@prisma/client";
@Service()
export default class AppointmentsRepository extends BaseRepository {
@ -24,24 +23,6 @@ export default class AppointmentsRepository extends BaseRepository {
return this.model.findMany(query);
}
/**
* @description : Create new appointment
*/
public async create(appointment: Appointment): Promise<Appointments> {
const createArgs: Prisma.AppointmentsCreateArgs = {
data: {
user: {
connect: {
uid: appointment.targeted_user!.uid,
},
},
choice: EVote[appointment.choice as keyof typeof EVote],
},
};
return this.model.create(createArgs);
}
/**
* @description : Update data of a appointment
*/

View File

@ -2,7 +2,7 @@ import Database from "@Common/databases/database";
import BaseRepository from "@Repositories/BaseRepository";
import { Service } from "typedi";
import { ECivility, Prisma, Users } from "@prisma/client";
import User from "le-coffre-resources/dist/SuperAdmin";
import User, { Role } from "le-coffre-resources/dist/SuperAdmin";
@Service()
export default class UsersRepository extends BaseRepository {
@ -199,7 +199,7 @@ export default class UsersRepository extends BaseRepository {
/**
* @description : Find one user with office
*/
public async findOneByUidWithRole(uid: string) {
public async findOneByUidWithRole(uid: string): Promise<((Users & {role: Role} )| null)> {
return this.model.findUnique({
where: {
uid: uid,

View File

@ -1,7 +1,7 @@
import Database from "@Common/databases/database";
import BaseRepository from "@Repositories/BaseRepository";
import { Service } from "typedi";
import { Votes, Prisma } from "@prisma/client";
import { Votes, Prisma, EVote, EAppointmentStatus } from "@prisma/client";
import { Vote } from "le-coffre-resources/dist/SuperAdmin";
@Service()
@ -27,12 +27,29 @@ export default class VotesRepository extends BaseRepository {
/**
* @description : Create new vote
*/
public async create(vote: Vote): Promise<Votes> {
public async create(vote: Vote): Promise<Votes> {
let whereArg: Prisma.AppointmentsWhereUniqueInput;
if(vote.appointment.targeted_user.uid) {
whereArg = {
user_uid_choice_status: {
user_uid: vote.appointment.targeted_user.uid,
choice: EVote[vote.appointment.choice as keyof typeof EVote],
status: EAppointmentStatus.OPEN,
}
};
} else {
whereArg = {
uid: vote.appointment.uid,
};
}
const createArgs: Prisma.VotesCreateArgs = {
data: {
appointment: {
connect: {
uid: vote.appointment.uid,
connectOrCreate: {
where: whereArg,
create: {
user_uid: vote.appointment.targeted_user.uid!,
}
},
},
voter: {
@ -40,10 +57,10 @@ export default class VotesRepository extends BaseRepository {
uid: vote.voter.uid,
},
},
},
}
};
return this.model.create(createArgs);
return this.model.create({...createArgs, include: {appointment: {include: {votes: true}}}});
}
/**

View File

@ -1,14 +1,11 @@
import BaseService from "@Services/BaseService";
import { Service } from "typedi";
import { Appointment } from "le-coffre-resources/dist/SuperAdmin";
import AppointmentsRepository from "@Repositories/AppointmentsRepository";
import { Prisma, Appointments, EAppointmentStatus } from "@prisma/client";
import UsersService from "../UsersService/UsersService";
import { EVote } from "le-coffre-resources/dist/SuperAdmin/Appointment";
@Service()
export default class AppointmentService extends BaseService {
constructor(private appointmentRepository: AppointmentsRepository, private userService: UsersService) {
constructor(private appointmentRepository: AppointmentsRepository) {
super();
}
@ -20,17 +17,6 @@ export default class AppointmentService extends BaseService {
return this.appointmentRepository.findMany(query);
}
/**
* @description : Create a appointment
* @throws {Error} If appointment couldn't be created
*/
public async create(appointment: Appointment): Promise<Appointments> {
const user = await this.userService.getByUidWithRole(appointment.targeted_user!.uid!)
if(!user) throw new Error("User not found");
user.role.name === "super-admin" ? appointment.choice = EVote.DISMISS : appointment.choice = EVote.NOMINATE;
return this.appointmentRepository.create(appointment);
}
/**
* @description : Modify a appointment
* @throws {Error} If appointment cannot be modified

View File

@ -0,0 +1,97 @@
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, Votes } from "@prisma/client";
import AppointmentService from "../AppointmentsService/AppointmentsService";
import UsersService from "../UsersService/UsersService";
import RolesService from "../RolesService/RolesService";
@Service()
export default class LiveVoteService extends BaseService {
constructor(
private voteRepository: VotesRepository,
private appointmentService: AppointmentService,
private userService: UsersService,
private roleService: RolesService,
) {
super();
}
public async verifyVoterChoice(vote: Vote): Promise<boolean> {
const userWithRole = await this.userService.getByUidWithRole(vote.appointment.targeted_user.uid!);
if (userWithRole!.role.name === "super-admin" && vote.appointment.choice === EVote.DISMISS) {
return true;
}
if (userWithRole!.role.name !== "super-admin" && vote.appointment.choice === EVote.NOMINATE) {
return true;
}
return false;
}
public async getAppointmentWithVotes(vote: Vote): Promise<Appointments | null> {
if (vote.appointment.uid) {
return this.appointmentService.getByUidWithVotes(vote.appointment.uid);
}
const appointmentByUser = await this.appointmentService.get({
where: {
AND: [
{ user_uid: vote.appointment.targeted_user.uid },
{ status: EAppointmentStatus.OPEN },
{ choice: EVote[vote.appointment.choice as keyof typeof EVote] },
],
},
include: { votes: true },
});
if (appointmentByUser.length >= 1) {
return this.appointmentService.getByUidWithVotes(appointmentByUser[0]!.uid);
}
return null;
}
private async closeVote(appointment: Appointments, vote: Votes) {
await this.appointmentService.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" });
return await this.updateRole(appointment, userEntity, vote);
}
private async updateRole(appointment: Appointments, userEntity: User, vote: Votes) {
if (appointment.choice === EVote.DISMISS) {
const roles = await this.roleService.get({ where: { name: "default" } });
const roleEntity = Role.hydrate<Role>(roles[0]!, { strategy: "excludeAll" });
userEntity.role = roleEntity;
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.userService.update(appointment!.user_uid, userEntity);
return vote;
}
return vote;
}
/**
* @description : Create a vote
* @throws {Error} If vote couldn't be created
*/
public async create(vote: Vote): Promise<Votes | null> {
const appointment = await this.getAppointmentWithVotes(vote);
if (appointment) {
const appointmentEntity = Appointment.hydrate<Appointment>(appointment, { strategy: "excludeAll" });
if (appointmentEntity?.votes && appointmentEntity.votes.length >= 2) {
const voteCreated = await this.voteRepository.create(vote);
return this.closeVote(appointment, voteCreated);
}
}
const approvedChoice = await this.verifyVoterChoice(vote);
if(!approvedChoice) return null;
return this.voteRepository.create(vote);
}
}

View File

@ -1,20 +1,11 @@
import BaseService from "@Services/BaseService";
import { Service } from "typedi";
import User, { Role, Vote } from "le-coffre-resources/dist/SuperAdmin";
import VotesRepository from "@Repositories/VotesRepository";
import { EAppointmentStatus, EVote, Prisma, Votes } from "@prisma/client";
import AppointmentService from "../AppointmentsService/AppointmentsService";
import UsersService from "../UsersService/UsersService";
import RolesService from "../RolesService/RolesService";
import { Prisma } from "@prisma/client";
@Service()
export default class VoteService extends BaseService {
constructor(
private voteRepository: VotesRepository,
private appointmentService: AppointmentService,
private userService: UsersService,
private roleService: RolesService,
) {
constructor(private voteRepository: VotesRepository) {
super();
}
@ -26,36 +17,6 @@ export default class VoteService extends BaseService {
return this.voteRepository.findMany(query);
}
/**
* @description : Create a vote
* @throws {Error} If vote couldn't be created
*/
public async create(vote: Vote): Promise<Votes> {
const appointment = await this.appointmentService.getByUidWithVotes(vote.appointment.uid!);
if (appointment!.votes.length >= 2) {
const voteCreated = await this.voteRepository.create(vote);
await this.appointmentService.update(appointment!.uid!, EAppointmentStatus.CLOSED);
const user = await this.userService.getByUid(appointment!.user_uid, { role: true });
const userEntity = User.hydrate<User>(user!, { strategy: "excludeAll" });
if (appointment!.choice === EVote.DISMISS) {
const roles = await this.roleService.get({ where: { name: "default" } });
const roleEntity = Role.hydrate<Role>(roles[0]!, { strategy: "excludeAll" });
userEntity.role = roleEntity;
await this.userService.update(appointment!.user_uid, userEntity);
return voteCreated;
} 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.userService.update(appointment!.user_uid, userEntity);
return voteCreated;
}
}
return this.voteRepository.create(vote);
}
/**
* @description : Get a vote by uid
* @throws {Error} If vote cannot be get by uid