diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 67695b8..4edd8ab 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -89,8 +89,7 @@ model Address { state String country String - institutions Institution[] - applicationAnswers ApplicationAnswer[] + institutions Institution[] @@unique([city, state, country]) } @@ -101,7 +100,7 @@ model ApplicationAnswer { updatedAt DateTime @updatedAt date DateTime - approved Boolean @default(false) + approved Boolean @default(false) itemAnswerGroups ItemAnswerGroup[] @@ -109,8 +108,8 @@ model ApplicationAnswer { user User? @relation(fields: [userId], references: [id], onDelete: SetNull) applicationId Int application Application @relation(fields: [applicationId], references: [id], onDelete: Cascade) - addressId Int? - address Address? @relation(fields: [addressId], references: [id], onDelete: SetNull) + coordinateId Int? + coordinate Coordinate? @relation(fields: [coordinateId], references: [id], onDelete: SetNull) } model ItemAnswer { @@ -432,3 +431,16 @@ model PageDependencyRule { @@unique([pageId, itemId, type]) } + +model Coordinate { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + latitude Float + longitude Float + + answers ApplicationAnswer[] + + @@unique([latitude, longitude]) +} diff --git a/src/controllers/applicationAnswerController.ts b/src/controllers/applicationAnswerController.ts index a0aeb86..89eaa16 100644 --- a/src/controllers/applicationAnswerController.ts +++ b/src/controllers/applicationAnswerController.ts @@ -204,10 +204,11 @@ const fields = { date: true, userId: true, applicationId: true, - addressId: true, createdAt: true, updatedAt: true, approved: true, + coordinateId: true, + coordinate: { select: { latitude: true, longitude: true } }, itemAnswerGroups: { select: { id: true, @@ -252,13 +253,14 @@ export const createApplicationAnswer = async (req: Request, res: Response) => { optionAnswers: yup.array().of(createOptionAnswerSchema).default([]), }) .noUnknown(); + const createCoordinateSchema = yup.object().shape({ latitude: yup.number(), longitude: yup.number() }).noUnknown(); const createApplicationAnswerSchema = yup .object() .shape({ id: yup.number(), date: yup.date().required(), applicationId: yup.number().required(), - addressId: yup.number(), + coordinate: createCoordinateSchema, itemAnswerGroups: yup.array().of(createItemAnswerGroupSchema).min(1).required(), }) .noUnknown(); @@ -276,11 +278,26 @@ export const createApplicationAnswer = async (req: Request, res: Response) => { const createdApplicationAnswer = await prismaClient.$transaction(async (prisma) => { const createdApplicationAnswer: ApplicationAnswer = await prisma.applicationAnswer.create({ data: { - id: applicationAnswer.id, date: applicationAnswer.date, - userId: user.id, - applicationId: applicationAnswer.applicationId, - addressId: applicationAnswer.addressId, + user: { connect: { id: user.id } }, + application: { connect: { id: applicationAnswer.applicationId } }, + coordinate: + applicationAnswer.coordinate.latitude !== undefined && applicationAnswer.coordinate.longitude !== undefined + ? { + connectOrCreate: { + where: { + latitude_longitude: { + latitude: applicationAnswer.coordinate.latitude, + longitude: applicationAnswer.coordinate.longitude, + }, + }, + create: { + latitude: applicationAnswer.coordinate.latitude, + longitude: applicationAnswer.coordinate.longitude, + }, + }, + } + : undefined, approved: false, }, }); @@ -386,11 +403,15 @@ export const updateApplicationAnswer = async (req: Request, res: Response): Prom optionAnswers: yup.array().of(updateOptionAnswerSchema).default([]), }) .noUnknown(); + const updateCoordinateSchema = yup + .object() + .shape({ latitude: yup.number().required(), longitude: yup.number().required() }) + .noUnknown(); const updateApplicationAnswerSchema = yup .object() .shape({ date: yup.date(), - addressId: yup.number(), + coordinate: updateCoordinateSchema, itemAnswerGroups: yup.array().of(updateItemAnswerGroupSchema).min(1).required(), }) .noUnknown(); @@ -409,7 +430,26 @@ export const updateApplicationAnswer = async (req: Request, res: Response): Prom // Update application answer await prisma.applicationAnswer.update({ where: { id: applicationAnswerId }, - data: { date: applicationAnswer.date, addressId: applicationAnswer.addressId }, + data: { + date: applicationAnswer.date, + coordinate: + applicationAnswer.coordinate.latitude !== undefined && applicationAnswer.coordinate.longitude !== undefined + ? { + connectOrCreate: { + where: { + latitude_longitude: { + latitude: applicationAnswer.coordinate.latitude, + longitude: applicationAnswer.coordinate.longitude, + }, + }, + create: { + latitude: applicationAnswer.coordinate.latitude, + longitude: applicationAnswer.coordinate.longitude, + }, + }, + } + : { disconnect: true }, + }, }); // Remove item answer groups that are not in the updated application answer await prisma.itemAnswerGroup.deleteMany({ diff --git a/src/controllers/applicationController.ts b/src/controllers/applicationController.ts index c3f5dfc..a05b5d9 100644 --- a/src/controllers/applicationController.ts +++ b/src/controllers/applicationController.ts @@ -120,6 +120,7 @@ const fields = { protocol: { select: { id: true, title: true, description: true } }, visibility: true, answersVisibility: true, + keepLocation: true, applier: { select: { id: true, username: true, institutionId: true } }, createdAt: true, updatedAt: true, @@ -192,7 +193,15 @@ const fieldsWProtocol = { const fieldsWAnswers = { ...fieldsWProtocol, - answers: { select: { id: true, date: true, user: { select: { id: true, username: true } } } }, + answers: { + select: { + id: true, + date: true, + user: { select: { id: true, username: true } }, + coordinate: { select: { latitude: true, longitude: true } }, + approved: true, + }, + }, }; export const createApplication = async (req: Request, res: Response) => { @@ -575,7 +584,10 @@ export const getApplicationWithAnswers = async (req: Request, res: Response): Pr } } applicationWithAnswers.answers = Object.fromEntries( - applicationWithAnswers.answers.map((answer: any) => [answer.id, { date: answer.date, user: answer.user }]) + applicationWithAnswers.answers.map((answer: any) => [ + answer.id, + { date: answer.date, user: answer.user, coordinate: answer.coordinate, approved: answer.approved }, + ]) ); res.status(200).json({ message: 'Application with answers found.', data: applicationWithAnswers }); diff --git a/src/routes/applicationAnswerRoutes.ts b/src/routes/applicationAnswerRoutes.ts index b2152b4..62b5831 100644 --- a/src/routes/applicationAnswerRoutes.ts +++ b/src/routes/applicationAnswerRoutes.ts @@ -18,6 +18,7 @@ import { getMyApplicationAnswers, getApplicationAnswer, deleteApplicationAnswer, + approveApplicationAnswer, } from '../controllers/applicationAnswerController'; /** @@ -435,6 +436,49 @@ const router = express.Router(); */ router.post('/createApplicationAnswer', passport.authenticate('jwt', { session: false }), uploader.any(), createApplicationAnswer); +/** + * @swagger + * /api/applicationAnswer/approveApplicationAnswer/{applicationAnswerId}: + * put: + * summary: Approve an application answer by id + * tags: [ApplicationAnswer] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: applicationAnswerId + * schema: + * type: integer + * required: true + * description: The id of the application answer to approve + * responses: + * 200: + * description: The application answer was successfully approved + * content: + * application/json: + * message: Application answer approved. + * data: + * $ref: '#/components/schemas/GetApplicationAnswer' + * 404: + * description: Application answer not found + * content: + * application/json: + * error: + * message: Application answer not found. + * 500: + * description: An error occurred while approving the application answer + * content: + * application/json: + * error: + * message: Internal server error. + */ +router.put( + '/approveApplicationAnswer/:applicationAnswerId', + passport.authenticate('jwt', { session: false }), + uploader.none(), + approveApplicationAnswer +); + /** * @swagger * /api/applicationAnswer/updateApplicationAnswer/{applicationAnswerId}: