Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions constants/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const API_RESPONSE_MESSAGES = {
APPLICATION_CREATED_SUCCESS: "Application created successfully",
APPLICATION_RETURN_SUCCESS: "Applications returned successfully",
NUDGE_SUCCESS: "Nudge sent successfully",
FEEDBACK_SUBMITTED_SUCCESS: "Application feedback submitted successfully",
};

const APPLICATION_ERROR_MESSAGES = {
Expand All @@ -26,7 +27,11 @@ const APPLICATION_ERROR_MESSAGES = {
NUDGE_ONLY_PENDING_ALLOWED: "Nudge unavailable. Only pending applications can be nudged.",
};

const NUDGE_APPLICATION_STATUS = {
const APPLICATION_LOG_MESSAGES = {
ERROR_SUBMITTING_FEEDBACK: "Error while submitting the application feedback",
};

const APPLICATION_STATUS = {
notFound: "notFound",
unauthorized: "unauthorized",
notPending: "notPending",
Expand All @@ -45,6 +50,7 @@ module.exports = {
APPLICATION_ROLES,
API_RESPONSE_MESSAGES,
APPLICATION_ERROR_MESSAGES,
APPLICATION_LOG_MESSAGES,
APPLICATION_REVIEW_CYCLE_START_DATE,
NUDGE_APPLICATION_STATUS,
APPLICATION_STATUS
};
41 changes: 35 additions & 6 deletions controllers/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { logType } = require("../constants/logs");
import { CustomRequest, CustomResponse } from "../types/global";
const { INTERNAL_SERVER_ERROR } = require("../constants/errorMessages");
const ApplicationModel = require("../models/applications");
const { API_RESPONSE_MESSAGES, APPLICATION_ERROR_MESSAGES, NUDGE_APPLICATION_STATUS } = require("../constants/application");
const { API_RESPONSE_MESSAGES, APPLICATION_ERROR_MESSAGES, APPLICATION_LOG_MESSAGES, APPLICATION_STATUS } = require("../constants/application");
const { createApplicationService } = require("../services/applicationService");
const { Conflict } = require("http-errors");
const logger = require("../utils/logger");
Expand Down Expand Up @@ -131,6 +131,34 @@ const updateApplication = async (req: CustomRequest, res: CustomResponse) => {
}
};

const submitApplicationFeedback = async (req: CustomRequest, res: CustomResponse) => {
try {
const { applicationId } = req.params;
const { status, feedback } = req.body;

const addApplicationFeedbackResult = await ApplicationModel.addApplicationFeedback({
applicationId,
status,
feedback,
reviewerName: req.userData.username,
});

switch (addApplicationFeedbackResult.status) {
case APPLICATION_STATUS.notFound:
return res.boom.notFound("Application not found");
case APPLICATION_STATUS.success:
return res.json({
message: API_RESPONSE_MESSAGES.FEEDBACK_SUBMITTED_SUCCESS,
});
default:
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
}
} catch (err) {
logger.error(`${APPLICATION_LOG_MESSAGES.ERROR_SUBMITTING_FEEDBACK}: ${err}`);
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
}
};

const getApplicationById = async (req: CustomRequest, res: CustomResponse) => {
try {
const { applicationId } = req.params;
Expand Down Expand Up @@ -160,15 +188,15 @@ const nudgeApplication = async (req: CustomRequest, res: CustomResponse) => {
});

switch (result.status) {
case NUDGE_APPLICATION_STATUS.notFound:
case APPLICATION_STATUS.notFound:
return res.boom.notFound("Application not found");
case NUDGE_APPLICATION_STATUS.unauthorized:
case APPLICATION_STATUS.unauthorized:
return res.boom.unauthorized("You are not authorized to nudge this application");
case NUDGE_APPLICATION_STATUS.notPending:
case APPLICATION_STATUS.notPending:
return res.boom.badRequest(APPLICATION_ERROR_MESSAGES.NUDGE_ONLY_PENDING_ALLOWED);
case NUDGE_APPLICATION_STATUS.tooSoon:
case APPLICATION_STATUS.tooSoon:
return res.boom.tooManyRequests(APPLICATION_ERROR_MESSAGES.NUDGE_TOO_SOON);
case NUDGE_APPLICATION_STATUS.success:
case APPLICATION_STATUS.success:
return res.json({
message: API_RESPONSE_MESSAGES.NUDGE_SUCCESS,
nudgeCount: result.nudgeCount,
Expand All @@ -189,4 +217,5 @@ module.exports = {
updateApplication,
getApplicationById,
nudgeApplication,
submitApplicationFeedback,
};
41 changes: 29 additions & 12 deletions middlewares/validators/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,21 +71,38 @@ const validateApplicationData = async (req: CustomRequest, res: CustomResponse,

const validateApplicationUpdateData = async (req: CustomRequest, res: CustomResponse, next: NextFunction) => {
const schema = joi
.object()
.strict()
.keys({
status: joi
.object({
status: joi
.string()
.valid(
APPLICATION_STATUS_TYPES.ACCEPTED,
APPLICATION_STATUS_TYPES.REJECTED,
APPLICATION_STATUS_TYPES.CHANGES_REQUESTED
)
.required()
.messages({
"any.required": "Status is required",
"any.only":
"Status must be one of: accepted, rejected, or changes_requested",
}),

feedback: joi.when("status", {
is: APPLICATION_STATUS_TYPES.CHANGES_REQUESTED,
then: joi
.string()
.min(1)
.optional()
.custom((value, helper) => {
if (!Object.values(APPLICATION_STATUS_TYPES).includes(value)) {
return helper.message("Status is not valid");
}
return value;
.required()
.messages({
"any.required":
"Feedback is required when status is changes_requested",
"string.min":
"Feedback cannot be empty when status is changes_requested",
}),
feedback: joi.string().min(1).optional(),
});
otherwise: joi.string().optional().allow(""),
}),
})
.strict();


try {
await schema.validateAsync(req.body);
Expand Down
62 changes: 56 additions & 6 deletions models/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { application } from "../types/application";
const firestore = require("../utils/firestore");
const logger = require("../utils/logger");
const ApplicationsModel = firestore.collection("applicants");
const { APPLICATION_STATUS_TYPES, NUDGE_APPLICATION_STATUS } = require("../constants/application");
const { APPLICATION_STATUS_TYPES, APPLICATION_STATUS } = require("../constants/application");
const { convertDaysToMilliseconds } = require("../utils/time");

const getAllApplications = async (limit: number, lastDocId?: string) => {
Expand Down Expand Up @@ -146,17 +146,17 @@ const nudgeApplication = async ({ applicationId, userId }: { applicationId: stri
const applicationDoc = await transaction.get(applicationRef);

if (!applicationDoc.exists) {
return { status: NUDGE_APPLICATION_STATUS.notFound };
return { status: APPLICATION_STATUS.notFound };
}

const application = applicationDoc.data();

if (application.userId !== userId) {
return { status: NUDGE_APPLICATION_STATUS.unauthorized };
return { status: APPLICATION_STATUS.unauthorized };
}

if (application.status !== APPLICATION_STATUS_TYPES.PENDING) {
return { status: NUDGE_APPLICATION_STATUS.notPending };
return { status: APPLICATION_STATUS.notPending };
}

const lastNudgeAt = application.lastNudgeAt;
Expand All @@ -165,7 +165,7 @@ const nudgeApplication = async ({ applicationId, userId }: { applicationId: stri
const timeDifference = currentTime - lastNudgeTimestamp;

if (timeDifference <= twentyFourHoursInMilliseconds) {
return { status: NUDGE_APPLICATION_STATUS.tooSoon };
return { status: APPLICATION_STATUS.tooSoon };
}
}

Expand All @@ -179,7 +179,7 @@ const nudgeApplication = async ({ applicationId, userId }: { applicationId: stri
});

return {
status: NUDGE_APPLICATION_STATUS.success,
status: APPLICATION_STATUS.success,
nudgeCount: updatedNudgeCount,
lastNudgeAt: newLastNudgeAt,
};
Expand All @@ -188,6 +188,55 @@ const nudgeApplication = async ({ applicationId, userId }: { applicationId: stri
return result;
};

const addApplicationFeedback = async ({
applicationId,
status,
feedback,
reviewerName,
}: {
applicationId: string;
status: string;
feedback?: string;
reviewerName: string;
}) => {
const addApplicationFeedbackResult = await firestore.runTransaction(async (transaction) => {
const applicationRef = ApplicationsModel.doc(applicationId);
const applicationDoc = await transaction.get(applicationRef);

if (!applicationDoc.exists) {
return { status: APPLICATION_STATUS.notFound };
}

const application = applicationDoc.data();
const existingFeedback = application.feedback || [];

const feedbackItem: {
status: string;
feedback?: string;
reviewerName: string;
createdAt: string;
} = {
status,
reviewerName,
createdAt: new Date().toISOString(),
};

if (feedback && feedback.trim()) {
feedbackItem.feedback = feedback.trim();
}

const updatedFeedback = [...existingFeedback, feedbackItem];

transaction.update(applicationRef, {
feedback: updatedFeedback,
status,
});

return { status: APPLICATION_STATUS.success };
});
return addApplicationFeedbackResult;
};

module.exports = {
getAllApplications,
getUserApplications,
Expand All @@ -196,4 +245,5 @@ module.exports = {
getApplicationsBasedOnStatus,
getApplicationById,
nudgeApplication,
addApplicationFeedback,
};
4 changes: 2 additions & 2 deletions routes/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ router.get(
router.get("/:applicationId", authenticate, authorizeRoles([SUPERUSER]), applications.getApplicationById);
router.post("/", authenticate, applicationValidator.validateApplicationData, applications.addApplication);
router.patch(
"/:applicationId",
"/:applicationId/feedback",
authenticate,
authorizeRoles([SUPERUSER]),
applicationValidator.validateApplicationUpdateData,
applications.updateApplication
applications.submitApplicationFeedback
);
router.patch("/:applicationId/nudge", authenticate, applications.nudgeApplication);

Expand Down
Loading
Loading