diff --git a/package.json b/package.json index 7372ff24..31b23e12 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/api/super-admin/AppointmentsController.ts b/src/app/api/super-admin/AppointmentsController.ts index fa143534..f2790755 100644 --- a/src/app/api/super-admin/AppointmentsController.ts +++ b/src/app/api/super-admin/AppointmentsController.ts @@ -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(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(appointmentEntityCreated, { - strategy: "excludeAll", - }); - - //success - this.httpCreated(response, appointment); - } catch (error) { - this.httpInternalError(response, error); - return; - } - } - /** * @description Get a specific appointment by uid */ diff --git a/src/app/api/super-admin/LiveVoteController.ts b/src/app/api/super-admin/LiveVoteController.ts new file mode 100644 index 00000000..e07b6eab --- /dev/null +++ b/src/app/api/super-admin/LiveVoteController.ts @@ -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(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(voteEntityCreated, { + strategy: "excludeAll", + }); + + //success + this.httpCreated(response, vote); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } +} diff --git a/src/app/api/super-admin/VotesController.ts b/src/app/api/super-admin/VotesController.ts index 71057e82..4f08ccf8 100644 --- a/src/app/api/super-admin/VotesController.ts +++ b/src/app/api/super-admin/VotesController.ts @@ -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(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(voteEntityCreated, { - strategy: "excludeAll", - }); - - //success - this.httpCreated(response, vote); - } catch (error) { - this.httpInternalError(response, error); - return; - } - } - /** * @description Get a specific vote by uid */ diff --git a/src/app/index.ts b/src/app/index.ts index a4b4d9a2..f550461c 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -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); diff --git a/src/common/repositories/AppointmentsRepository.ts b/src/common/repositories/AppointmentsRepository.ts index 9d5102bc..62b74d34 100644 --- a/src/common/repositories/AppointmentsRepository.ts +++ b/src/common/repositories/AppointmentsRepository.ts @@ -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 { - 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 */ diff --git a/src/common/repositories/UsersRepository.ts b/src/common/repositories/UsersRepository.ts index 8545627c..49673146 100644 --- a/src/common/repositories/UsersRepository.ts +++ b/src/common/repositories/UsersRepository.ts @@ -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, diff --git a/src/common/repositories/VotesRepository.ts b/src/common/repositories/VotesRepository.ts index 2283ca94..0450760a 100644 --- a/src/common/repositories/VotesRepository.ts +++ b/src/common/repositories/VotesRepository.ts @@ -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 { + public async create(vote: Vote): Promise { + 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}}}}); } /** diff --git a/src/services/super-admin/AppointmentsService/AppointmentsService.ts b/src/services/super-admin/AppointmentsService/AppointmentsService.ts index 9a47953e..45d8b481 100644 --- a/src/services/super-admin/AppointmentsService/AppointmentsService.ts +++ b/src/services/super-admin/AppointmentsService/AppointmentsService.ts @@ -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 { - 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 diff --git a/src/services/super-admin/LiveVoteService/LiveVoteService.ts b/src/services/super-admin/LiveVoteService/LiveVoteService.ts new file mode 100644 index 00000000..5576bb28 --- /dev/null +++ b/src/services/super-admin/LiveVoteService/LiveVoteService.ts @@ -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 { + 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 { + 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!, { 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(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(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 { + const appointment = await this.getAppointmentWithVotes(vote); + + if (appointment) { + const appointmentEntity = Appointment.hydrate(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); + } +} diff --git a/src/services/super-admin/VotesService/VotesService.ts b/src/services/super-admin/VotesService/VotesService.ts index 663058ef..fa32fb4d 100644 --- a/src/services/super-admin/VotesService/VotesService.ts +++ b/src/services/super-admin/VotesService/VotesService.ts @@ -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 { - 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!, { strategy: "excludeAll" }); - - if (appointment!.choice === EVote.DISMISS) { - const roles = await this.roleService.get({ where: { name: "default" } }); - const roleEntity = Role.hydrate(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(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