From 59f1fe275839c569327347c0694794d7d3ae19d0 Mon Sep 17 00:00:00 2001 From: OxSaitama Date: Thu, 13 Jul 2023 13:31:15 +0200 Subject: [PATCH 1/7] add vote service --- package.json | 2 +- .../api/super-admin/AppointmentsController.ts | 103 ++++++++++++ src/app/api/super-admin/UsersController.ts | 8 +- src/app/api/super-admin/VotesController.ts | 147 ++++++++++++++++++ src/app/index.ts | 4 + .../20230713145026_v12/migration.sql | 38 +++++ .../20230725084826_v12/migration.sql | 45 ++++++ .../20230725151748_v13/migration.sql | 11 ++ src/common/databases/schema.prisma | 33 ++++ .../repositories/AppointmentsRepository.ts | 95 +++++++++++ src/common/repositories/UsersRepository.ts | 12 ++ src/common/repositories/VotesRepository.ts | 71 +++++++++ .../AppointmentsService.ts | 67 ++++++++ .../super-admin/UsersService/UsersService.ts | 8 + .../super-admin/VotesService/VotesService.ts | 73 +++++++++ 15 files changed, 714 insertions(+), 3 deletions(-) create mode 100644 src/app/api/super-admin/AppointmentsController.ts create mode 100644 src/app/api/super-admin/VotesController.ts create mode 100644 src/common/databases/migrations/20230713145026_v12/migration.sql create mode 100644 src/common/databases/migrations/20230725084826_v12/migration.sql create mode 100644 src/common/databases/migrations/20230725151748_v13/migration.sql create mode 100644 src/common/repositories/AppointmentsRepository.ts create mode 100644 src/common/repositories/VotesRepository.ts create mode 100644 src/services/super-admin/AppointmentsService/AppointmentsService.ts create mode 100644 src/services/super-admin/VotesService/VotesService.ts diff --git a/package.json b/package.json index fe9c92be..7372ff24 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.63", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.64", "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 new file mode 100644 index 00000000..936774c2 --- /dev/null +++ b/src/app/api/super-admin/AppointmentsController.ts @@ -0,0 +1,103 @@ +import { Response, Request } from "express"; +import { Controller, Get, Post } 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"; + +@Controller() +@Service() +export default class AppointmentsController extends ApiController { + constructor(private appointmentsService: AppointmentsService) { + super(); + } + + /** + * @description Get all appointments + */ + @Get("/api/v1/super-admin/appointments", [authHandler]) + protected async get(req: Request, response: Response) { + try { + //get query + let query; + if (req.query["q"]) { + query = JSON.parse(req.query["q"] as string); + } + + //call service to get prisma entity + const appointmentsEntities = await this.appointmentsService.get(query); + + //Hydrate ressource with prisma entity + const appointments = Appointment.hydrateArray(appointmentsEntities, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response, appointments); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + /** + * @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"]}); + + //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 + */ + @Get("/api/v1/super-admin/appointments/:uid", [authHandler]) + protected async getOneByUid(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } + + let query; + if (req.query["q"]) { + query = JSON.parse(req.query["q"] as string); + } + + const appointmentEntity = await this.appointmentsService.getByUid(uid, query); + + if (!appointmentEntity) { + this.httpNotFoundRequest(response, "appointment not found"); + return; + } + + //Hydrate ressource with prisma entity + const appointment = Appointment.hydrate(appointmentEntity, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response, appointment); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } +} diff --git a/src/app/api/super-admin/UsersController.ts b/src/app/api/super-admin/UsersController.ts index a96b3f5a..87d46cad 100644 --- a/src/app/api/super-admin/UsersController.ts +++ b/src/app/api/super-admin/UsersController.ts @@ -90,12 +90,13 @@ export default class UsersController extends ApiController { protected async put(req: Request, response: Response) { try { const uid = req.params["uid"]; + if (!uid) { this.httpBadRequest(response, "No uid provided"); return; } - const userFound = await this.usersService.getByUid(uid); + const userFound = await this.usersService.getByUid(uid, {role: true, votes: true}); if (!userFound) { this.httpNotFoundRequest(response, "user not found"); @@ -104,12 +105,15 @@ export default class UsersController extends ApiController { //init IUser resource with request body values const userEntity = User.hydrate(req.body); + const userFoundEntity = User.hydrate(userFound, { strategy: "excludeAll" }); //validate user await validateOrReject(userEntity, { groups: ["updateUser"] }); + const userEntityToUpdate = this.voteService.vote(userEntity, userFoundEntity, userId); + //call service to get prisma entity - const userEntityUpdated = await this.usersService.update(uid, userEntity); + const userEntityUpdated = await this.usersService.update(uid, userFoundEntity); //Hydrate ressource with prisma entity const user = User.hydrate(userEntityUpdated, { diff --git a/src/app/api/super-admin/VotesController.ts b/src/app/api/super-admin/VotesController.ts new file mode 100644 index 00000000..eac8b6a9 --- /dev/null +++ b/src/app/api/super-admin/VotesController.ts @@ -0,0 +1,147 @@ +import { Response, Request } from "express"; +import { Controller, Delete, Get, 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 { Votes } from "@prisma/client"; + +@Controller() +@Service() +export default class VotesController extends ApiController { + constructor(private votesService: VotesService, private usersService: UsersService) { + super(); + } + + /** + * @description Get all votes + */ + @Get("/api/v1/super-admin/votes", [authHandler]) + protected async get(req: Request, response: Response) { + try { + //get query + let query; + if (req.query["q"]) { + query = JSON.parse(req.query["q"] as string); + } + + //call service to get prisma entity + const votesEntities = await this.votesService.get(query); + + //Hydrate ressource with prisma entity + const votes = Vote.hydrateArray(votesEntities, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response, votes); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + /** + * @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 votes = await this.votesService.get({ where: { AND: [{ appointment: {uid: voteEntity.uid } }, {voter: {uid: userId}}]}}); + console.log(votes) + if (votes.length) throw new Error("Voter already voted for this appointment"); + + 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 + */ + @Get("/api/v1/super-admin/votes/:uid", [authHandler]) + protected async getOneByUid(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } + + let query; + if (req.query["q"]) { + query = JSON.parse(req.query["q"] as string); + } + + const voteEntity = await this.votesService.getByUid(uid, query); + + if (!voteEntity) { + this.httpNotFoundRequest(response, "vote not found"); + return; + } + + //Hydrate ressource with prisma entity + const vote = Vote.hydrate(voteEntity, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response, vote); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + /** + * @description Delete a specific folder + */ + @Delete("/api/v1/super-admin/votes/:uid", [authHandler]) + protected async delete(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } + + const voteFound = await this.votesService.getByUid(uid); + + if (!voteFound) { + this.httpNotFoundRequest(response, "vote not found"); + return; + } + + //call service to get prisma entity + const votetEntity: Votes = await this.votesService.delete(uid); + + //Hydrate ressource with prisma entity + const vote = Vote.hydrate(votetEntity, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response, vote); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + +} diff --git a/src/app/index.ts b/src/app/index.ts index 22f4d1f3..a4b4d9a2 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -40,6 +40,8 @@ import RolesControllerNotary from "./api/notary/RolesController"; import OfficeRolesControllerNotary from "./api/notary/OfficeRolesController"; 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"; /** @@ -56,6 +58,8 @@ export default { Container.get(DeedTypesControllerSuperAdmin); Container.get(DocumentsControllerSuperAdmin); Container.get(DocumentTypesControllerSuperAdmin); + Container.get(AppointmentsController); + Container.get(VotesController); Container.get(IdNotUserController); Container.get(FranceConnectCustomerController); Container.get(FilesControllerSuperAdmin); diff --git a/src/common/databases/migrations/20230713145026_v12/migration.sql b/src/common/databases/migrations/20230713145026_v12/migration.sql new file mode 100644 index 00000000..c5dac8d4 --- /dev/null +++ b/src/common/databases/migrations/20230713145026_v12/migration.sql @@ -0,0 +1,38 @@ +-- DropForeignKey +ALTER TABLE "users" DROP CONSTRAINT "users_contact_uid_fkey"; + +-- DropForeignKey +ALTER TABLE "users" DROP CONSTRAINT "users_office_role_uid_fkey"; + +-- DropForeignKey +ALTER TABLE "users" DROP CONSTRAINT "users_office_uid_fkey"; + +-- DropForeignKey +ALTER TABLE "users" DROP CONSTRAINT "users_roles_uid_fkey"; + +-- CreateTable +CREATE TABLE "votes" ( + "uid" TEXT NOT NULL, + "user_uid" VARCHAR(255) NOT NULL, + "voters" TEXT[], + + CONSTRAINT "votes_pkey" PRIMARY KEY ("uid") +); + +-- CreateIndex +CREATE UNIQUE INDEX "votes_uid_key" ON "votes"("uid"); + +-- AddForeignKey +ALTER TABLE "users" ADD CONSTRAINT "users_contact_uid_fkey" FOREIGN KEY ("contact_uid") REFERENCES "contacts"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "users" ADD CONSTRAINT "users_roles_uid_fkey" FOREIGN KEY ("roles_uid") REFERENCES "roles"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "users" ADD CONSTRAINT "users_office_role_uid_fkey" FOREIGN KEY ("office_role_uid") REFERENCES "office_roles"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "users" ADD CONSTRAINT "users_office_uid_fkey" FOREIGN KEY ("office_uid") REFERENCES "offices"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "votes" ADD CONSTRAINT "votes_user_uid_fkey" FOREIGN KEY ("user_uid") REFERENCES "users"("uid") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/common/databases/migrations/20230725084826_v12/migration.sql b/src/common/databases/migrations/20230725084826_v12/migration.sql new file mode 100644 index 00000000..0b7f1455 --- /dev/null +++ b/src/common/databases/migrations/20230725084826_v12/migration.sql @@ -0,0 +1,45 @@ +/* + Warnings: + + - You are about to drop the column `user_uid` on the `votes` table. All the data in the column will be lost. + - You are about to drop the column `voters` on the `votes` table. All the data in the column will be lost. + - Added the required column `appointment_uid` to the `votes` table without a default value. This is not possible if the table is not empty. + - Added the required column `voter_uid` to the `votes` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateEnum +CREATE TYPE "EVote" AS ENUM ('NOMINATE', 'DISMISS'); + +-- CreateEnum +CREATE TYPE "EAppointmentStatus" AS ENUM ('OPEN', 'CLOSED'); + +-- DropForeignKey +ALTER TABLE "votes" DROP CONSTRAINT "votes_user_uid_fkey"; + +-- AlterTable +ALTER TABLE "votes" DROP COLUMN "user_uid", +DROP COLUMN "voters", +ADD COLUMN "appointment_uid" VARCHAR(255) NOT NULL, +ADD COLUMN "choice" "EVote" NOT NULL DEFAULT 'NOMINATE', +ADD COLUMN "voter_uid" VARCHAR(255) NOT NULL; + +-- CreateTable +CREATE TABLE "appointments" ( + "uid" TEXT NOT NULL, + "user_uid" VARCHAR(255) NOT NULL, + "status" "EAppointmentStatus" NOT NULL DEFAULT 'OPEN', + + CONSTRAINT "appointments_pkey" PRIMARY KEY ("uid") +); + +-- CreateIndex +CREATE UNIQUE INDEX "appointments_uid_key" ON "appointments"("uid"); + +-- AddForeignKey +ALTER TABLE "appointments" ADD CONSTRAINT "appointments_user_uid_fkey" FOREIGN KEY ("user_uid") REFERENCES "users"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "votes" ADD CONSTRAINT "votes_appointment_uid_fkey" FOREIGN KEY ("appointment_uid") REFERENCES "appointments"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "votes" ADD CONSTRAINT "votes_voter_uid_fkey" FOREIGN KEY ("voter_uid") REFERENCES "users"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/src/common/databases/migrations/20230725151748_v13/migration.sql b/src/common/databases/migrations/20230725151748_v13/migration.sql new file mode 100644 index 00000000..b8c667d7 --- /dev/null +++ b/src/common/databases/migrations/20230725151748_v13/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - You are about to drop the column `choice` on the `votes` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "appointments" ADD COLUMN "choice" "EVote" NOT NULL DEFAULT 'NOMINATE'; + +-- AlterTable +ALTER TABLE "votes" DROP COLUMN "choice"; diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index 53eae7d8..ae182a9d 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -65,6 +65,8 @@ model Users { office_uid String @db.VarChar(255) notifications Notifications[] @relation("UserHasNotifications") office_folders OfficeFolders[] @relation("OfficeFolderHasStakeholders") + appointment Appointments[] + votes Votes[] @@map("users") } @@ -287,6 +289,27 @@ model Emails { @@map("email") } +model Appointments { + uid String @id @unique @default(uuid()) + user Users @relation(fields: [user_uid], references: [uid], onDelete: Cascade) + user_uid String @db.VarChar(255) + choice EVote @default(NOMINATE) + status EAppointmentStatus @default(OPEN) + votes Votes[] + + @@map("appointments") +} + +model Votes { + uid String @id @unique @default(uuid()) + appointment Appointments @relation(fields: [appointment_uid], references: [uid], onDelete: Cascade) + appointment_uid String @db.VarChar(255) + voter Users @relation(fields: [voter_uid], references: [uid]) + voter_uid String @db.VarChar(255) + + @@map("votes") +} + enum ECivility { MALE FEMALE @@ -321,3 +344,13 @@ enum EDocumentStatus { ANCHORED REFUSED } + +enum EVote { + NOMINATE + DISMISS +} + +enum EAppointmentStatus { + OPEN + CLOSED +} diff --git a/src/common/repositories/AppointmentsRepository.ts b/src/common/repositories/AppointmentsRepository.ts new file mode 100644 index 00000000..9d5102bc --- /dev/null +++ b/src/common/repositories/AppointmentsRepository.ts @@ -0,0 +1,95 @@ +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"; + +@Service() +export default class AppointmentsRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().appointments; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many appointments + */ + public async findMany(query: Prisma.AppointmentsFindManyArgs) { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + 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 + */ + public async update(uid: string, status: EAppointmentStatus): Promise { + const updateArgs: Prisma.AppointmentsUpdateArgs = { + where: { + uid: uid, + }, + data: { + status: status, + }, + }; + + return this.model.update(updateArgs); + } + + /** + * @description : Find one appointment + */ + public async findOneByUid(uid: string, query?: Prisma.AppointmentsInclude) { + return this.model.findUnique({ + where: { + uid: uid, + }, + include: query, + }); + } + + /** + * @description : Find one appointment with votes + */ + public async findOneByUidWithVotes(uid: string) { + return this.model.findUnique({ + where: { + uid: uid, + }, + include: {votes: true}, + }); + } + + /** + * @description : delete a appointment + */ + public async delete(uid: string): Promise { + return this.model.delete({ + where: { + uid: uid, + }, + }); + } +} diff --git a/src/common/repositories/UsersRepository.ts b/src/common/repositories/UsersRepository.ts index b472a8ec..8545627c 100644 --- a/src/common/repositories/UsersRepository.ts +++ b/src/common/repositories/UsersRepository.ts @@ -196,6 +196,18 @@ export default class UsersRepository extends BaseRepository { }); } + /** + * @description : Find one user with office + */ + public async findOneByUidWithRole(uid: string) { + return this.model.findUnique({ + where: { + uid: uid, + }, + include: { role: true }, + }); + } + /** * @description : Find one user */ diff --git a/src/common/repositories/VotesRepository.ts b/src/common/repositories/VotesRepository.ts new file mode 100644 index 00000000..2283ca94 --- /dev/null +++ b/src/common/repositories/VotesRepository.ts @@ -0,0 +1,71 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { Votes, Prisma } from "@prisma/client"; +import { Vote } from "le-coffre-resources/dist/SuperAdmin"; + +@Service() +export default class VotesRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().votes; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many votes + */ + public async findMany(query: Prisma.VotesFindManyArgs) { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Create new vote + */ + public async create(vote: Vote): Promise { + const createArgs: Prisma.VotesCreateArgs = { + data: { + appointment: { + connect: { + uid: vote.appointment.uid, + }, + }, + voter: { + connect: { + uid: vote.voter.uid, + }, + }, + }, + }; + + return this.model.create(createArgs); + } + + /** + * @description : Find one vote + */ + public async findOneByUid(uid: string, query?: Prisma.VotesInclude): Promise { + return this.model.findUnique({ + where: { + uid: uid, + }, + include: query, + }); + } + + /** + * @description : delete a vote + */ + public async delete(uid: string): Promise { + return this.model.delete({ + where: { + uid: uid, + }, + }); + } +} diff --git a/src/services/super-admin/AppointmentsService/AppointmentsService.ts b/src/services/super-admin/AppointmentsService/AppointmentsService.ts new file mode 100644 index 00000000..9a47953e --- /dev/null +++ b/src/services/super-admin/AppointmentsService/AppointmentsService.ts @@ -0,0 +1,67 @@ +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) { + super(); + } + + /** + * @description : Get all appointments + * @throws {Error} If appointments cannot be get + */ + public get(query: Prisma.AppointmentsFindManyArgs) { + 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 + */ + public async update(uid: string, status: EAppointmentStatus): Promise { + return this.appointmentRepository.update(uid, status); + } + + /** + * @description : Get a appointment by uid + * @throws {Error} If appointment cannot be get by uid + */ + public getByUid(uid: string, query?: Prisma.AppointmentsInclude): Promise { + return this.appointmentRepository.findOneByUid(uid, query); + } + + /** + * @description : Get a appointment by uid + * @throws {Error} If appointment cannot be get by uid + */ + public getByUidWithVotes(uid: string) { + return this.appointmentRepository.findOneByUidWithVotes(uid); + } + + /** + * @description : delete a appointment by uid + * @throws {Error} If appointment cannot be get by uid + */ + public delete(uid: string) { + return this.appointmentRepository.delete(uid); + } + + +} diff --git a/src/services/super-admin/UsersService/UsersService.ts b/src/services/super-admin/UsersService/UsersService.ts index da350fea..241cb1e4 100644 --- a/src/services/super-admin/UsersService/UsersService.ts +++ b/src/services/super-admin/UsersService/UsersService.ts @@ -51,6 +51,14 @@ export default class UsersService extends BaseService { return this.userRepository.findOneByUidWithOffice(uid); } + /** + * @description : Get a user by uid with role + * @throws {Error} If user cannot be get by uid + */ + public getByUidWithRole(uid: string) { + return this.userRepository.findOneByUidWithRole(uid); + } + /** * @description : Get a user by uid * @throws {Error} If user cannot be get by uid diff --git a/src/services/super-admin/VotesService/VotesService.ts b/src/services/super-admin/VotesService/VotesService.ts new file mode 100644 index 00000000..bdd95fe4 --- /dev/null +++ b/src/services/super-admin/VotesService/VotesService.ts @@ -0,0 +1,73 @@ +import BaseService from "@Services/BaseService"; +import { Service } from "typedi"; +import { 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"; + +@Service() +export default class VoteService extends BaseService { + constructor( + private voteRepository: VotesRepository, + private appointmentService: AppointmentService, + private userService: UsersService, + private roleService: RolesService, + ) { + super(); + } + + /** + * @description : Get all votes + * @throws {Error} If votes cannot be get + */ + public get(query: Prisma.VotesFindManyArgs) { + 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) throw new Error("Appointment not found"); + if (appointment.status === EAppointmentStatus.CLOSED) throw new Error("Appointment is closed"); + + 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); + + if (appointment.choice === EVote.DISMISS) { + const roles = await this.roleService.get({ where: { name: "default" } }); + user!.roles_uid = roles[0]!.uid; + await this.userService.update(appointment.user_uid, user!); + return voteCreated; + } else if (appointment.choice === EVote.NOMINATE) { + const roles = await this.roleService.get({ where: { name: "super-admin" } }); + user!.roles_uid = roles[0]!.uid; + await this.userService.update(appointment.user_uid, user!); + return voteCreated; + } + } + return this.voteRepository.create(vote); + } + + /** + * @description : Get a vote by uid + * @throws {Error} If vote cannot be get by uid + */ + public getByUid(uid: string, query?: Prisma.VotesInclude) { + return this.voteRepository.findOneByUid(uid, query); + } + + /** + * @description : delete a vote by uid + * @throws {Error} If vote cannot be get by uid + */ + public delete(uid: string) { + return this.voteRepository.delete(uid); + } +} From 0a675027ca03b749419dfa31e7a90a067e755bf0 Mon Sep 17 00:00:00 2001 From: OxSaitama Date: Tue, 25 Jul 2023 18:56:47 +0200 Subject: [PATCH 2/7] fix merge issue --- src/app/api/super-admin/UsersController.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/api/super-admin/UsersController.ts b/src/app/api/super-admin/UsersController.ts index 87d46cad..71d97022 100644 --- a/src/app/api/super-admin/UsersController.ts +++ b/src/app/api/super-admin/UsersController.ts @@ -109,8 +109,6 @@ export default class UsersController extends ApiController { //validate user await validateOrReject(userEntity, { groups: ["updateUser"] }); - - const userEntityToUpdate = this.voteService.vote(userEntity, userFoundEntity, userId); //call service to get prisma entity const userEntityUpdated = await this.usersService.update(uid, userFoundEntity); From e9f3fb7e9c475ae216ff4843e775640ad57b4bac Mon Sep 17 00:00:00 2001 From: OxSaitama Date: Wed, 26 Jul 2023 11:49:32 +0200 Subject: [PATCH 3/7] add error handling --- .../api/super-admin/AppointmentsController.ts | 9 ++++- src/app/api/super-admin/VotesController.ts | 35 +++++++++++++------ .../super-admin/VotesService/VotesService.ts | 25 ++++++------- 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/app/api/super-admin/AppointmentsController.ts b/src/app/api/super-admin/AppointmentsController.ts index 936774c2..fa143534 100644 --- a/src/app/api/super-admin/AppointmentsController.ts +++ b/src/app/api/super-admin/AppointmentsController.ts @@ -6,11 +6,12 @@ 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) { + constructor(private appointmentsService: AppointmentsService, private usersService: UsersService) { super(); } @@ -51,6 +52,12 @@ export default class AppointmentsController extends ApiController { //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 diff --git a/src/app/api/super-admin/VotesController.ts b/src/app/api/super-admin/VotesController.ts index eac8b6a9..71057e82 100644 --- a/src/app/api/super-admin/VotesController.ts +++ b/src/app/api/super-admin/VotesController.ts @@ -7,12 +7,13 @@ 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 { Votes } from "@prisma/client"; +import { EAppointmentStatus, Votes } from "@prisma/client"; +import AppointmentService from "@Services/super-admin/AppointmentsService/AppointmentsService"; @Controller() @Service() export default class VotesController extends ApiController { - constructor(private votesService: VotesService, private usersService: UsersService) { + constructor(private votesService: VotesService, private usersService: UsersService, private appointmentService: AppointmentService) { super(); } @@ -52,14 +53,29 @@ export default class VotesController extends ApiController { //init IUser resource with request body values const voteEntity = Vote.hydrate(req.body); //validate user - await validateOrReject(voteEntity, { groups: ["createVote"]}); + 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 votes = await this.votesService.get({ where: { AND: [{ appointment: {uid: voteEntity.uid } }, {voter: {uid: userId}}]}}); - console.log(votes) - if (votes.length) throw new Error("Voter already voted for this appointment"); - const voter = await this.usersService.getByUid(userId); - + voteEntity.voter = voter!; //call service to get prisma entity const voteEntityCreated = await this.votesService.create(voteEntity); @@ -92,7 +108,7 @@ export default class VotesController extends ApiController { if (req.query["q"]) { query = JSON.parse(req.query["q"] as string); } - + const voteEntity = await this.votesService.getByUid(uid, query); if (!voteEntity) { @@ -143,5 +159,4 @@ export default class VotesController extends ApiController { return; } } - } diff --git a/src/services/super-admin/VotesService/VotesService.ts b/src/services/super-admin/VotesService/VotesService.ts index bdd95fe4..663058ef 100644 --- a/src/services/super-admin/VotesService/VotesService.ts +++ b/src/services/super-admin/VotesService/VotesService.ts @@ -1,6 +1,6 @@ import BaseService from "@Services/BaseService"; import { Service } from "typedi"; -import { Vote } from "le-coffre-resources/dist/SuperAdmin"; +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"; @@ -32,23 +32,24 @@ export default class VoteService extends BaseService { */ public async create(vote: Vote): Promise { const appointment = await this.appointmentService.getByUidWithVotes(vote.appointment.uid!); - if (!appointment) throw new Error("Appointment not found"); - if (appointment.status === EAppointmentStatus.CLOSED) throw new Error("Appointment is closed"); - if (appointment.votes.length >= 2) { + 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); + 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) { + if (appointment!.choice === EVote.DISMISS) { const roles = await this.roleService.get({ where: { name: "default" } }); - user!.roles_uid = roles[0]!.uid; - await this.userService.update(appointment.user_uid, user!); + 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) { + } else if (appointment!.choice === EVote.NOMINATE) { const roles = await this.roleService.get({ where: { name: "super-admin" } }); - user!.roles_uid = roles[0]!.uid; - await this.userService.update(appointment.user_uid, user!); + const roleEntity = Role.hydrate(roles[0]!, { strategy: "excludeAll" }); + userEntity!.role = roleEntity; + await this.userService.update(appointment!.user_uid, userEntity); return voteCreated; } } From d244158c4ec31a3bae9ea6b10dbf7ff60a3b5683 Mon Sep 17 00:00:00 2001 From: OxSaitama Date: Wed, 26 Jul 2023 11:53:20 +0200 Subject: [PATCH 4/7] add unique constraint on appointments --- .../databases/migrations/20230726095252_v25/migration.sql | 8 ++++++++ src/common/databases/schema.prisma | 1 + 2 files changed, 9 insertions(+) create mode 100644 src/common/databases/migrations/20230726095252_v25/migration.sql diff --git a/src/common/databases/migrations/20230726095252_v25/migration.sql b/src/common/databases/migrations/20230726095252_v25/migration.sql new file mode 100644 index 00000000..389c4749 --- /dev/null +++ b/src/common/databases/migrations/20230726095252_v25/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[user_uid,choice,status]` on the table `appointments` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "appointments_user_uid_choice_status_key" ON "appointments"("user_uid", "choice", "status"); diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index ae182a9d..98ff92ea 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -297,6 +297,7 @@ model Appointments { status EAppointmentStatus @default(OPEN) votes Votes[] + @@unique([user_uid, choice, status]) @@map("appointments") } From fffcd17944892fd89dde3098c0a0dab130477239 Mon Sep 17 00:00:00 2001 From: OxSaitama Date: Wed, 26 Jul 2023 17:11:32 +0200 Subject: [PATCH 5/7] refacto vote creation in live-votes --- package.json | 2 +- .../api/super-admin/AppointmentsController.ts | 38 +------- src/app/api/super-admin/LiveVoteController.ts | 90 +++++++++++++++++ src/app/api/super-admin/VotesController.ts | 58 +---------- src/app/index.ts | 2 + .../repositories/AppointmentsRepository.ts | 21 +--- src/common/repositories/UsersRepository.ts | 4 +- src/common/repositories/VotesRepository.ts | 29 ++++-- .../AppointmentsService.ts | 16 +-- .../LiveVoteService/LiveVoteService.ts | 97 +++++++++++++++++++ .../super-admin/VotesService/VotesService.ts | 43 +------- 11 files changed, 224 insertions(+), 176 deletions(-) create mode 100644 src/app/api/super-admin/LiveVoteController.ts create mode 100644 src/services/super-admin/LiveVoteService/LiveVoteService.ts 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 From 3b7b9288bc6c285458bc748d30a8511406eba03a Mon Sep 17 00:00:00 2001 From: Maxime Lalo Date: Thu, 27 Jul 2023 13:20:13 +0200 Subject: [PATCH 6/7] :bug: Fixing you already voted for this appointment --- package-lock.json | 185 +++++++++++------- package.json | 2 +- src/app/api/super-admin/LiveVoteController.ts | 62 +++--- src/common/repositories/VotesRepository.ts | 5 +- 4 files changed, 146 insertions(+), 108 deletions(-) diff --git a/package-lock.json b/package-lock.json index a6b784ab..0290f4ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,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.57", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.66", "module-alias": "^2.2.2", "multer": "^1.4.5-lts.1", "next": "^13.1.5", @@ -1061,14 +1061,14 @@ } }, "node_modules/@next/env": { - "version": "13.4.10", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.10.tgz", - "integrity": "sha512-3G1yD/XKTSLdihyDSa8JEsaWOELY+OWe08o0LUYzfuHp1zHDA8SObQlzKt+v+wrkkPcnPweoLH1ImZeUa0A1NQ==" + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.12.tgz", + "integrity": "sha512-RmHanbV21saP/6OEPBJ7yJMuys68cIf8OBBWd7+uj40LdpmswVAwe1uzeuFyUsd6SfeITWT3XnQfn6wULeKwDQ==" }, "node_modules/@next/swc-darwin-arm64": { - "version": "13.4.10", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.10.tgz", - "integrity": "sha512-4bsdfKmmg7mgFGph0UorD1xWfZ5jZEw4kKRHYEeTK9bT1QnMbPVPlVXQRIiFPrhoDQnZUoa6duuPUJIEGLV1Jg==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.12.tgz", + "integrity": "sha512-deUrbCXTMZ6ZhbOoloqecnUeNpUOupi8SE2tx4jPfNS9uyUR9zK4iXBvH65opVcA/9F5I/p8vDXSYbUlbmBjZg==", "cpu": [ "arm64" ], @@ -1081,9 +1081,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "13.4.10", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.10.tgz", - "integrity": "sha512-ngXhUBbcZIWZWqNbQSNxQrB9T1V+wgfCzAor2olYuo/YpaL6mUYNUEgeBMhr8qwV0ARSgKaOp35lRvB7EmCRBg==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.12.tgz", + "integrity": "sha512-WRvH7RxgRHlC1yb5oG0ZLx8F7uci9AivM5/HGGv9ZyG2Als8Ij64GC3d+mQ5sJhWjusyU6T6V1WKTUoTmOB0zQ==", "cpu": [ "x64" ], @@ -1096,9 +1096,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.4.10", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.10.tgz", - "integrity": "sha512-SjCZZCOmHD4uyM75MVArSAmF5Y+IJSGroPRj2v9/jnBT36SYFTORN8Ag/lhw81W9EeexKY/CUg2e9mdebZOwsg==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.12.tgz", + "integrity": "sha512-YEKracAWuxp54tKiAvvq73PUs9lok57cc8meYRibTWe/VdPB2vLgkTVWFcw31YDuRXdEhdX0fWS6Q+ESBhnEig==", "cpu": [ "arm64" ], @@ -1111,9 +1111,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.4.10", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.10.tgz", - "integrity": "sha512-F+VlcWijX5qteoYIOxNiBbNE8ruaWuRlcYyIRK10CugqI/BIeCDzEDyrHIHY8AWwbkTwe6GRHabMdE688Rqq4Q==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.12.tgz", + "integrity": "sha512-LhJR7/RAjdHJ2Isl2pgc/JaoxNk0KtBgkVpiDJPVExVWA1c6gzY57+3zWuxuyWzTG+fhLZo2Y80pLXgIJv7g3g==", "cpu": [ "arm64" ], @@ -1126,9 +1126,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.4.10", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.10.tgz", - "integrity": "sha512-WDv1YtAV07nhfy3i1visr5p/tjiH6CeXp4wX78lzP1jI07t4PnHHG1WEDFOduXh3WT4hG6yN82EQBQHDi7hBrQ==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.12.tgz", + "integrity": "sha512-1DWLL/B9nBNiQRng+1aqs3OaZcxC16Nf+mOnpcrZZSdyKHek3WQh6j/fkbukObgNGwmCoVevLUa/p3UFTTqgqg==", "cpu": [ "x64" ], @@ -1141,9 +1141,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "13.4.10", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.10.tgz", - "integrity": "sha512-zFkzqc737xr6qoBgDa3AwC7jPQzGLjDlkNmt/ljvQJ/Veri5ECdHjZCUuiTUfVjshNIIpki6FuP0RaQYK9iCRg==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.12.tgz", + "integrity": "sha512-kEAJmgYFhp0VL+eRWmUkVxLVunn7oL9Mdue/FS8yzRBVj7Z0AnIrHpTIeIUl1bbdQq1VaoOztnKicAjfkLTRCQ==", "cpu": [ "x64" ], @@ -1156,9 +1156,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.4.10", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.10.tgz", - "integrity": "sha512-IboRS8IWz5mWfnjAdCekkl8s0B7ijpWeDwK2O8CdgZkoCDY0ZQHBSGiJ2KViAG6+BJVfLvcP+a2fh6cdyBr9QQ==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.12.tgz", + "integrity": "sha512-GMLuL/loR6yIIRTnPRY6UGbLL9MBdw2anxkOnANxvLvsml4F0HNIgvnU3Ej4BjbqMTNjD4hcPFdlEow4XHPdZA==", "cpu": [ "arm64" ], @@ -1171,9 +1171,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.4.10", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.10.tgz", - "integrity": "sha512-bSA+4j8jY4EEiwD/M2bol4uVEu1lBlgsGdvM+mmBm/BbqofNBfaZ2qwSbwE2OwbAmzNdVJRFRXQZ0dkjopTRaQ==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.12.tgz", + "integrity": "sha512-PhgNqN2Vnkm7XaMdRmmX0ZSwZXQAtamBVSa9A/V1dfKQCV1rjIZeiy/dbBnVYGdj63ANfsOR/30XpxP71W0eww==", "cpu": [ "ia32" ], @@ -1186,9 +1186,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.4.10", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.10.tgz", - "integrity": "sha512-g2+tU63yTWmcVQKDGY0MV1PjjqgZtwM4rB1oVVi/v0brdZAcrcTV+04agKzWtvWroyFz6IqtT0MoZJA7PNyLVw==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.12.tgz", + "integrity": "sha512-Z+56e/Ljt0bUs+T+jPjhFyxYBcdY2RIq9ELFU+qAMQMteHo7ymbV7CKmlcX59RI9C4YzN8PgMgLyAoi916b5HA==", "cpu": [ "x64" ], @@ -1493,9 +1493,9 @@ } }, "node_modules/@types/node": { - "version": "18.17.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.0.tgz", - "integrity": "sha512-GXZxEtOxYGFchyUzxvKI14iff9KZ2DI+A6a37o6EQevtg6uO9t+aUZKcaC1Te5Ng1OnLM7K9NVVj+FbecD9cJg==" + "version": "18.17.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.1.tgz", + "integrity": "sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==" }, "node_modules/@types/node-fetch": { "version": "2.6.4", @@ -2049,9 +2049,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001516", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001516.tgz", - "integrity": "sha512-Wmec9pCBY8CWbmI4HsjBeQLqDTqV91nFVR83DnZpYyRnPI1wePDsTg0bGLPC5VU/3OIZV1fmxEea1b+tFKe86g==", + "version": "1.0.30001517", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz", + "integrity": "sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==", "funding": [ { "type": "opencollective", @@ -2335,9 +2335,9 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, "node_modules/cron": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/cron/-/cron-2.3.1.tgz", - "integrity": "sha512-1eRRlIT0UfIqauwbG9pkg3J6CX9A6My2ytJWqAXoK0T9oJnUZTzGBNPxao0zjodIbPgf8UQWjE62BMb9eVllSQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-2.4.0.tgz", + "integrity": "sha512-Cx77ic1TyIAtUggr0oAhtS8MLzPBUqGNIvdDM7jE3oFIxfe8LXWI9q3iQN/H2CebAiMir53LQKWOhEKnzkJTAQ==", "dependencies": { "luxon": "^3.2.1" } @@ -2467,9 +2467,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.461", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.461.tgz", - "integrity": "sha512-1JkvV2sgEGTDXjdsaQCeSwYYuhLRphRpc+g6EHTFELJXEiznLt3/0pZ9JuAOQ5p2rI3YxKTbivtvajirIfhrEQ==", + "version": "1.4.473", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.473.tgz", + "integrity": "sha512-aVfC8+440vGfl06l8HKKn8/PD5jRfSnLkTTD65EFvU46igbpQRri1gxSzW9/+TeUlwYzrXk1sw867T96zlyECA==", "dev": true }, "node_modules/emittery": { @@ -3193,17 +3193,17 @@ } }, "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/istanbul-lib-source-maps": { @@ -3244,9 +3244,9 @@ "dev": true }, "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -3972,7 +3972,7 @@ } }, "node_modules/le-coffre-resources": { - "resolved": "git+ssh://git@github.com/smart-chain-fr/leCoffre-resources.git#dffc2429ba6ceb4129deb56e308962799b9b572c", + "resolved": "git+ssh://git@github.com/smart-chain-fr/leCoffre-resources.git#e7916d516fe434c93ef13e765d8d63a7ce3c56b2", "license": "MIT", "dependencies": { "class-transformer": "^0.5.1", @@ -3990,9 +3990,9 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.10.37", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.37.tgz", - "integrity": "sha512-Z10PCaOCiAxbUxLyR31DNeeNugSVP6iv/m7UrSKS5JHziEMApJtgku4e9Q69pzzSC9LnQiM09sqsGf2ticZnMw==" + "version": "1.10.38", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.38.tgz", + "integrity": "sha512-4NjVXVUmpZ9Zsqq6FXa2+MKI+KAI3tOqA0pxXgXGluhpj4ge5didmbWJpMBqGB3AVGv1SnEtKdGTbxjSEG1kCQ==" }, "node_modules/lines-and-columns": { "version": "1.2.4", @@ -4066,20 +4066,53 @@ } }, "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "dependencies": { - "semver": "^6.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-dir/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -4386,11 +4419,11 @@ } }, "node_modules/next": { - "version": "13.4.10", - "resolved": "https://registry.npmjs.org/next/-/next-13.4.10.tgz", - "integrity": "sha512-4ep6aKxVTQ7rkUW2fBLhpBr/5oceCuf4KmlUpvG/aXuDTIf9mexNSpabUD6RWPspu6wiJJvozZREhXhueYO36A==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/next/-/next-13.4.12.tgz", + "integrity": "sha512-eHfnru9x6NRmTMcjQp6Nz0J4XH9OubmzOa7CkWL+AUrUxpibub3vWwttjduu9No16dug1kq04hiUUpo7J3m3Xw==", "dependencies": { - "@next/env": "13.4.10", + "@next/env": "13.4.12", "@swc/helpers": "0.5.1", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", @@ -4406,15 +4439,15 @@ "node": ">=16.8.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "13.4.10", - "@next/swc-darwin-x64": "13.4.10", - "@next/swc-linux-arm64-gnu": "13.4.10", - "@next/swc-linux-arm64-musl": "13.4.10", - "@next/swc-linux-x64-gnu": "13.4.10", - "@next/swc-linux-x64-musl": "13.4.10", - "@next/swc-win32-arm64-msvc": "13.4.10", - "@next/swc-win32-ia32-msvc": "13.4.10", - "@next/swc-win32-x64-msvc": "13.4.10" + "@next/swc-darwin-arm64": "13.4.12", + "@next/swc-darwin-x64": "13.4.12", + "@next/swc-linux-arm64-gnu": "13.4.12", + "@next/swc-linux-arm64-musl": "13.4.12", + "@next/swc-linux-x64-gnu": "13.4.12", + "@next/swc-linux-x64-musl": "13.4.12", + "@next/swc-win32-arm64-msvc": "13.4.12", + "@next/swc-win32-ia32-msvc": "13.4.12", + "@next/swc-win32-x64-msvc": "13.4.12" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -5661,9 +5694,9 @@ } }, "node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" }, "node_modules/type-detect": { "version": "4.0.8", diff --git a/package.json b/package.json index 31b23e12..17bcb5c6 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.65", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.66", "module-alias": "^2.2.2", "multer": "^1.4.5-lts.1", "next": "^13.1.5", diff --git a/src/app/api/super-admin/LiveVoteController.ts b/src/app/api/super-admin/LiveVoteController.ts index e07b6eab..0e3307f4 100644 --- a/src/app/api/super-admin/LiveVoteController.ts +++ b/src/app/api/super-admin/LiveVoteController.ts @@ -1,20 +1,25 @@ -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 ApiController from "@Common/system/controller-pattern/ApiController"; +import { Controller, Post } from "@ControllerPattern/index"; import { EAppointmentStatus } from "@prisma/client"; import AppointmentService from "@Services/super-admin/AppointmentsService/AppointmentsService"; import LiveVoteService from "@Services/super-admin/LiveVoteService/LiveVoteService"; +import UsersService from "@Services/super-admin/UsersService/UsersService"; +import VotesService from "@Services/super-admin/VotesService/VotesService"; +import { validateOrReject } from "class-validator"; +import { Request, Response } from "express"; +import { Vote } from "le-coffre-resources/dist/SuperAdmin"; +import { Service } from "typedi"; @Controller() @Service() export default class LiveVoteController extends ApiController { - constructor(private liveVoteService: LiveVoteService, private votesService: VotesService, private usersService: UsersService, private appointmentService: AppointmentService) { + constructor( + private liveVoteService: LiveVoteService, + private votesService: VotesService, + private usersService: UsersService, + private appointmentService: AppointmentService, + ) { super(); } @@ -30,6 +35,7 @@ export default class LiveVoteController extends ApiController { //validate user await validateOrReject(voteEntity, { groups: ["createVote"] }); + let voteFound = []; if (voteEntity.appointment.uid) { const appointment = await this.appointmentService.getByUid(voteEntity.appointment.uid); if (!appointment) { @@ -40,27 +46,25 @@ export default class LiveVoteController extends ApiController { this.httpBadRequest(response, "Appointment is closed"); return; } + voteFound = await this.votesService.get({ + where: { AND: [{ appointment: { uid: voteEntity.appointment.uid } }, { voter: { uid: userId } }] }, + }); + } else { + voteFound = await this.votesService.get({ + where: { + AND: [ + { + appointment: { + AND: [{ user_uid: voteEntity.appointment.targeted_user.uid }, { status: EAppointmentStatus.OPEN }], + }, + }, + { voter: { uid: userId } }, + ], + }, + }); } - 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) { + if (voteFound.length) { this.httpBadRequest(response, "Voter already voted for this appointment"); return; } @@ -71,7 +75,7 @@ export default class LiveVoteController extends ApiController { //call service to get prisma entity const voteEntityCreated = await this.liveVoteService.create(voteEntity); - if(!voteEntityCreated) { + if (!voteEntityCreated) { this.httpBadRequest(response, "Appointment choice is not valid"); return; } diff --git a/src/common/repositories/VotesRepository.ts b/src/common/repositories/VotesRepository.ts index 0450760a..a9bf7746 100644 --- a/src/common/repositories/VotesRepository.ts +++ b/src/common/repositories/VotesRepository.ts @@ -1,8 +1,8 @@ import Database from "@Common/databases/database"; +import { EAppointmentStatus, EVote, Prisma, Votes } from "@prisma/client"; import BaseRepository from "@Repositories/BaseRepository"; -import { Service } from "typedi"; -import { Votes, Prisma, EVote, EAppointmentStatus } from "@prisma/client"; import { Vote } from "le-coffre-resources/dist/SuperAdmin"; +import { Service } from "typedi"; @Service() export default class VotesRepository extends BaseRepository { @@ -48,6 +48,7 @@ export default class VotesRepository extends BaseRepository { connectOrCreate: { where: whereArg, create: { + choice: EVote[vote.appointment.choice as keyof typeof EVote], user_uid: vote.appointment.targeted_user.uid!, } }, From 69afd66d2ea015ea18dea379d3e5bdb6e7806d96 Mon Sep 17 00:00:00 2001 From: Maxime Lalo Date: Thu, 27 Jul 2023 15:26:41 +0200 Subject: [PATCH 7/7] :sparkles: Modify seeder --- src/common/databases/seeders/seeder.ts | 34 ++++++-------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/src/common/databases/seeders/seeder.ts b/src/common/databases/seeders/seeder.ts index cf0129f4..941942db 100644 --- a/src/common/databases/seeders/seeder.ts +++ b/src/common/databases/seeders/seeder.ts @@ -1,18 +1,7 @@ +import { ECivility, ECustomerStatus, EFolderStatus, EOfficeStatus, Prisma, PrismaClient } from "@prisma/client"; +import User, { Address, Contact, Customer, Deed, DeedType, DocumentType, Office, OfficeFolder, OfficeRole, Role, Rule } from "le-coffre-resources/dist/SuperAdmin"; + import "module-alias/register"; -import { EFolderStatus, EOfficeStatus, ECivility, ECustomerStatus, PrismaClient, Prisma } from "@prisma/client"; -import User, { - Address, - Contact, - Customer, - Deed, - DeedType, - Office, - OfficeFolder, - OfficeRole, - Role, - Rule, - DocumentType, -} from "le-coffre-resources/dist/SuperAdmin"; export default async function main() { try{ @@ -605,7 +594,7 @@ export default async function main() { updated_at: new Date(), }, { - name: "PUT deedtypes", + name: "PUT deed-types", created_at: new Date(), updated_at: new Date(), }, @@ -665,7 +654,7 @@ export default async function main() { updated_at: new Date(), }, { - name: "POST deedtypes", + name: "POST deed-types", created_at: new Date(), updated_at: new Date(), }, @@ -862,26 +851,19 @@ export default async function main() { rules: rules.slice(0, 40), }, { - name: "notary", + name: "Notaire", created_at: new Date(), updated_at: new Date(), office: offices[0]!, rules: rules.slice(0, 33), }, { - name: "deputy", + name: "Collaborateur", created_at: new Date(), updated_at: new Date(), office: offices[0]!, rules: rules.slice(0, 22), - }, - { - name: "listener", - created_at: new Date(), - updated_at: new Date(), - office: offices[0]!, - rules: rules.slice(0, 11), - }, + } ]; const users: User[] = [