Skip to content

Commit 560d885

Browse files
authored
add confirmdeny (#69)
1 parent 90690b5 commit 560d885

File tree

7 files changed

+139
-3
lines changed

7 files changed

+139
-3
lines changed

app/config/swaggerconfig.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ const swaggerDefinition = {
225225
},
226226
"status": {
227227
"type": "string",
228-
"enum": ["PENDING", "ACCEPTED", "WAITLISTED", "REJECTED"],
228+
"enum": ["PENDING", "ACCEPTED", "WAITLISTED", "REJECTED", "CONFIRMED", "DECLINED"],
229229
"default": "PENDING",
230230
"description": "Current status of the application",
231231
"example": "PENDING"

app/constants/ApplicationStatus.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ const ApplicationStatus = {
33
ACCEPTED: "ACCEPTED",
44
REJECTED: "REJECTED",
55
WAITLISTED: "WAITLISTED",
6+
CONFIRMED: "CONFIRMED",
7+
DECLINED: "DECLINED",
68
}
79

810
export default ApplicationStatus;

app/controllers/Application.controller.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import prismaInstance from "../database/Prisma.js";
22
import logger from "../utils/logger.js";
33
import { uploadFileToAzure, deleteFileFromAzure, generateTemporaryUrl } from "../utils/azureStorage.js";
44
import { transformApplicationData } from "../utils/formDataTransform.js";
5+
import ApplicationStatus from "../constants/ApplicationStatus.js";
56

67
const prisma = prismaInstance;
78

@@ -221,6 +222,13 @@ export const updateApplication = async (req, res) => {
221222
});
222223
}
223224

225+
// have to check for status field since status can be updated only on certain conditions
226+
if (req.body.status) {
227+
return res.status(400).json({
228+
message: "Status field cannot be updated via this endpoint"
229+
});
230+
}
231+
224232
const application = await prisma.application.findUnique({
225233
where: {
226234
id: req.params.id,
@@ -554,3 +562,71 @@ export const getResumeUrl = async (req, res) => {
554562
});
555563
}
556564
};
565+
566+
export const confirmOrDenyApplication = async (req, res) => {
567+
try {
568+
if (!req.params.id) {
569+
return res.status(400).json({
570+
message: "Application id parameter is required"
571+
});
572+
}
573+
574+
const { status } = req.body;
575+
576+
if (!status || ![ApplicationStatus.CONFIRMED, ApplicationStatus.DECLINED].includes(status)) {
577+
return res.status(400).json({
578+
message: "Invalid status. Allowed values are 'CONFIRMED' or 'DECLINED'."
579+
});
580+
}
581+
582+
const application = await prisma.application.findUnique({
583+
where: {
584+
id: req.params.id,
585+
}
586+
});
587+
588+
if (!application) {
589+
return res.status(404).json({
590+
message: "Application not found"
591+
});
592+
}
593+
594+
// Authorization check - only the user who owns the application can update it
595+
if (req.user.id !== application.userId) {
596+
logger.warn(`Unauthorized attempt to update application status for id ${req.params.id}`);
597+
return res.status(403).json({
598+
message: "You are not authorized to update this application."
599+
});
600+
}
601+
602+
603+
// Verify current status is "ACCEPTED"
604+
if (application.status !== ApplicationStatus.ACCEPTED) {
605+
return res.status(400).json({
606+
message: "Application status must be 'ACCEPTED' to update to 'CONFIRMED' or 'DECLINED'."
607+
});
608+
}
609+
610+
const updatedApplication = await prisma.application.update({
611+
where: {
612+
id: req.params.id,
613+
},
614+
data: {
615+
status: status,
616+
}
617+
});
618+
619+
logger.info(`Application status updated to '${status}' for application id ${req.params.id}`);
620+
return res.status(200).json({
621+
message: `Application status updated to '${status}' successfully.`,
622+
application: updatedApplication,
623+
});
624+
} catch (error) {
625+
logger.error('Error in confirmOrDenyApplication:', error);
626+
return res.status(500).json({
627+
message: "Something went wrong",
628+
error: error.message,
629+
});
630+
}
631+
632+
}

app/database/Prisma.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const applicationSchema = z.object({
6161
whyBostonhacks: z.string().min(10, "Please provide a more detailed response"),
6262
applicationYear: z.number().int().min(2023).max(new Date().getFullYear() + 1),
6363
userId: z.string().uuid("Must be a valid user ID").readonly(),
64-
status: StatusSchema.default(ApplicationStatus.PENDING).readonly(),
64+
status: StatusSchema.default(ApplicationStatus.PENDING),
6565
resumeUrl: z.string("Resume URL must be valid").readonly(),
6666
authorizeMLHEmail: z.boolean().default(false),
6767
});
@@ -196,7 +196,6 @@ const applicationUpdateSchema = applicationSchema.omit({
196196
userId: true,
197197
applicationYear: true,
198198
id: true,
199-
status: true
200199
}).partial().strict();
201200
const projectUpdateSchema = projectSchema.omit({
202201
id: true,

app/routes/Application.routes.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
updateApplication,
66
getResumeUrl,
77
deleteResume,
8+
confirmOrDenyApplication
89
} from "../controllers/Application.controller.js";
910
import express from "express";
1011
import { verifyToken } from "../middleware/verifyToken.js";
@@ -353,4 +354,50 @@ router.get("/:id/resume/url", verifyToken, getResumeUrl);
353354
*/
354355
router.delete("/:id/resume", verifyToken, deleteResume);
355356

357+
358+
/**
359+
* @openapi
360+
*
361+
* /application/{id}/confirmdeny:
362+
* put:
363+
* summary: Confirm or deny an application
364+
* description: Update the status of an application to either "CONFIRMED" or "DECLINED" only if the current status is "ACCEPTED". This is the only way a user can change their application status.
365+
* tags: [Application]
366+
* parameters:
367+
* - $ref: "#/components/parameters/access_token"
368+
* - in: path
369+
* name: id
370+
* description: Application id
371+
* required: true
372+
* requestBody:
373+
* required: true
374+
* content:
375+
* application/json:
376+
* schema:
377+
* type: object
378+
* properties:
379+
* status:
380+
* type: string
381+
* enum: [CONFIRMED, DECLINED]
382+
* description: New status for the application
383+
* responses:
384+
* 200:
385+
* description: Application successfully updated
386+
* content:
387+
* application/json:
388+
* schema:
389+
* $ref: "#/components/schemas/Application"
390+
* 400:
391+
* description: Invalid input or application status not "ACCEPTED"
392+
* 401:
393+
* $ref: "#/components/responses/401unauthorized"
394+
* 403:
395+
* $ref: "#/components/responses/403forbidden"
396+
* 404:
397+
* description: Application not found
398+
* 500:
399+
* $ref: "#/components/responses/500internalservererror"
400+
*/
401+
router.put("/:id/confirmdeny", verifyToken, confirmOrDenyApplication);
402+
356403
export default router;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- AlterEnum
2+
-- This migration adds more than one value to an enum.
3+
-- With PostgreSQL versions 11 and earlier, this is not possible
4+
-- in a single migration. This can be worked around by creating
5+
-- multiple migrations, each migration adding only one value to
6+
-- the enum.
7+
8+
9+
ALTER TYPE "Status" ADD VALUE 'CONFIRMED';
10+
ALTER TYPE "Status" ADD VALUE 'DECLINED';

prisma/schema.prisma

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ enum Status {
139139
ACCEPTED
140140
WAITLISTED
141141
REJECTED
142+
CONFIRMED
143+
DECLINED
142144
}
143145

144146
enum AuthProvider {

0 commit comments

Comments
 (0)