Skip to content
Open
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
4 changes: 4 additions & 0 deletions constants/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export const REQUEST_LOG_TYPE = {
REQUEST_CANCELLED: "REQUEST_CANCELLED",
REQUEST_UPDATED: "REQUEST_UPDATED",
PENDING_REQUEST_FOUND: "PENDING_REQUEST_FOUND",
REQUEST_ALREADY_APPROVED: "REQUEST_ALREADY_APPROVED",
REQUEST_ALREADY_REJECTED: "REQUEST_ALREADY_REJECTED",
};

export const REQUEST_CREATED_SUCCESSFULLY = "Request created successfully";
Expand All @@ -39,7 +41,9 @@ export const REQUEST_ALREADY_REJECTED = "Request already rejected";
export const ERROR_WHILE_FETCHING_REQUEST = "Error while fetching request";
export const ERROR_WHILE_CREATING_REQUEST = "Error while creating request";
export const ERROR_WHILE_UPDATING_REQUEST = "Error while updating request";
export const ERROR_WHILE_ACKNOWLEDGING_REQUEST = "Error while acknowledging request";

export const REQUEST_ID_REQUIRED = "Request id is required";
export const REQUEST_DOES_NOT_EXIST = "Request does not exist";
export const REQUEST_ALREADY_PENDING = "Request already exists please wait for approval or rejection";
export const UNAUTHORIZED_TO_CREATE_OOO_REQUEST = "Unauthorized to create OOO request";
Expand Down
93 changes: 77 additions & 16 deletions controllers/oooRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import {
REQUEST_ALREADY_PENDING,
USER_STATUS_NOT_FOUND,
OOO_STATUS_ALREADY_EXIST,
UNAUTHORIZED_TO_UPDATE_REQUEST,
ERROR_WHILE_ACKNOWLEDGING_REQUEST,
REQUEST_ID_REQUIRED,
} from "../constants/requests";
import { statusState } from "../constants/userStatus";
import { logType } from "../constants/logs";
Expand All @@ -20,17 +23,24 @@ import { getRequestByKeyValues, getRequests, updateRequest } from "../models/req
import { createUserFutureStatus } from "../models/userFutureStatus";
import { getUserStatus, addFutureStatus } from "../models/userStatus";
import { createOooRequest, validateUserStatus } from "../services/oooRequest";
import * as oooRequestService from "../services/oooRequest";
import { CustomResponse } from "../typeDefinitions/global";
import { OooRequestCreateRequest, OooRequestResponse, OooStatusRequest } from "../types/oooRequest";
import {
AcknowledgeOooRequest,
OooRequestCreateRequest,
OooRequestResponse,
OooStatusRequest,
} from "../types/oooRequest";
import { UpdateRequest } from "../types/requests";
import { NextFunction } from "express";

/**
* Controller to handle the creation of OOO requests.
*
*
* This function processes the request to create an OOO request,
* validates the user status, checks existing requests,
* and stores the new request in the database with logging.
*
*
* @param {OooRequestCreateRequest} req - The Express request object containing the body with OOO details.
* @param {CustomResponse} res - The Express response object used to send back the response.
* @returns {Promise<OooRequestResponse>} Resolves to a response with the success or an error message.
Expand All @@ -39,7 +49,6 @@ export const createOooRequestController = async (
req: OooRequestCreateRequest,
res: OooRequestResponse
): Promise<OooRequestResponse> => {

const requestBody = req.body;
const { id: userId, username } = req.userData;
const isUserPartOfDiscord = req.userData.roles.in_discord;
Expand All @@ -57,25 +66,26 @@ export const createOooRequestController = async (

if (validationResponse) {
if (validationResponse.error === USER_STATUS_NOT_FOUND) {
return res.boom.notFound(validationResponse.error);
return res.boom.notFound(validationResponse.error);
}
if (validationResponse.error === OOO_STATUS_ALREADY_EXIST) {
return res.boom.forbidden(validationResponse.error);
return res.boom.forbidden(validationResponse.error);
}
}

const latestOooRequest: OooStatusRequest = await getRequestByKeyValues({
userId,
type: REQUEST_TYPE.OOO,
status: REQUEST_STATE.PENDING,
requestedBy: userId,
type: REQUEST_TYPE.OOO,
status: REQUEST_STATE.PENDING,
});

if (latestOooRequest) {
await addLog(logType.PENDING_REQUEST_FOUND,
{ userId, oooRequestId: latestOooRequest.id },
{ message: REQUEST_ALREADY_PENDING }
);
return res.boom.conflict(REQUEST_ALREADY_PENDING);
await addLog(
logType.PENDING_REQUEST_FOUND,
{ userId, oooRequestId: latestOooRequest.id },
{ message: REQUEST_ALREADY_PENDING }
);
return res.boom.conflict(REQUEST_ALREADY_PENDING);
}

await createOooRequest(requestBody, username, userId);
Expand Down Expand Up @@ -103,7 +113,7 @@ export const updateOooRequestController = async (req: UpdateRequest, res: Custom
return res.boom.badRequest(requestResult.error);
}
const [logType, returnMessage] =
requestResult.state === REQUEST_STATE.APPROVED
requestResult.status === REQUEST_STATE.APPROVED
? [REQUEST_LOG_TYPE.REQUEST_APPROVED, REQUEST_APPROVED_SUCCESSFULLY]
: [REQUEST_LOG_TYPE.REQUEST_REJECTED, REQUEST_REJECTED_SUCCESSFULLY];

Expand All @@ -118,7 +128,7 @@ export const updateOooRequestController = async (req: UpdateRequest, res: Custom
body: requestResult,
};
await addLog(requestLog.type, requestLog.meta, requestLog.body);
if (requestResult.state === REQUEST_STATE.APPROVED) {
if (requestResult.status === REQUEST_STATE.APPROVED) {
const requestData = await getRequests({ id: requestId });

if (requestData) {
Expand Down Expand Up @@ -148,3 +158,54 @@ export const updateOooRequestController = async (req: UpdateRequest, res: Custom
return res.boom.badImplementation(ERROR_WHILE_UPDATING_REQUEST);
}
};

/**
* Acknowledges an Out-of-Office (OOO) request.
* Devflag and superuser checks are handled by conditionalOooChecks middleware.
*
* @param {AcknowledgeOooRequest} req - The request object containing request parameters and user data
* @param {OooRequestResponse} res - The response object
* @param {NextFunction} next - Express next function for error handling
* @returns {Promise<OooRequestResponse | void>} The response object or void on error
*/
export const acknowledgeOooRequest = async (
req: AcknowledgeOooRequest,
res: OooRequestResponse,
next: NextFunction
): Promise<OooRequestResponse> => {
try {

const requestBody = req.body;
const superUserId = req.userData.id;
const requestId = req.params.id;

if (!requestId) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can perform this check if the validator

return res.boom.badRequest(REQUEST_ID_REQUIRED);
}

const response = await oooRequestService.acknowledgeOooRequest(requestId, requestBody, superUserId);

return res.status(200).json({
message: response.message,
});
} catch (error) {
logger.error(ERROR_WHILE_ACKNOWLEDGING_REQUEST, {
error: error.message,
requestId: req.params.id,
superUserId: req.userData?.id,
requestType: req.body?.type,
userAgent: req.get("User-Agent"),
timestamp: new Date().toISOString(),
});


if (error.statusCode === 409) {
return res.boom.conflict(error.message);
} else if (error.statusCode === 400) {
return res.boom.badRequest(error.message);
} else {

next(new Error(ERROR_WHILE_ACKNOWLEDGING_REQUEST));
}
}
};
22 changes: 13 additions & 9 deletions controllers/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
} from "../constants/requests";
import { getRequests } from "../models/requests";
import { getPaginatedLink } from "../utils/helper";
import { createOooRequestController, updateOooRequestController } from "./oooRequests";
import { OooRequestCreateRequest, OooRequestResponse } from "../types/oooRequest";
import { acknowledgeOooRequest, createOooRequestController, updateOooRequestController } from "./oooRequests";
import { AcknowledgeOooRequest, OooRequestCreateRequest, OooRequestResponse } from "../types/oooRequest";
import { CustomResponse } from "../typeDefinitions/global";
import { ExtensionRequestRequest, ExtensionRequestResponse } from "../types/extensionRequests";
import { createTaskExtensionRequest, updateTaskExtensionRequest } from "./extensionRequestsv2";
Expand All @@ -16,8 +16,7 @@ import { createTaskRequestController } from "./taskRequestsv2";
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionStateRequest } from "../types/onboardingExtension";
import { createOnboardingExtensionRequestController, updateOnboardingExtensionRequestController, updateOnboardingExtensionRequestState } from "./onboardingExtension";
import { UpdateOnboardingExtensionRequest } from "../types/onboardingExtension";

import { Request } from "express";
import { NextFunction, Request } from "express";

export const createRequestController = async (
req: OooRequestCreateRequest | ExtensionRequestRequest | TaskRequestRequest | OnboardingExtensionCreateRequest,
Expand Down Expand Up @@ -67,10 +66,10 @@ export const getRequestsController = async (req: any, res: any) => {
});
}

const { allRequests, next, prev, page } = requests;
if (allRequests.length === 0) {
return res.status(204).send();
}
const { allRequests, next, prev, page } = requests;
if (allRequests.length === 0) {
return res.status(204).send();
}

if (page) {
const pageLink = `/requests?page=${page}`;
Expand Down Expand Up @@ -108,6 +107,7 @@ export const getRequestsController = async (req: any, res: any) => {
next: nextUrl,
prev: prevUrl,
});

} catch (err) {
logger.error(ERROR_WHILE_FETCHING_REQUEST, err);
return res.boom.badImplementation(ERROR_WHILE_FETCHING_REQUEST);
Expand All @@ -121,9 +121,13 @@ export const getRequestsController = async (req: any, res: any) => {
* @param {CustomResponse} res - The response object.
* @returns {Promise<void>} Resolves or sends an error for invalid types.
*/
export const updateRequestBeforeAcknowledgedController = async (req: Request, res: CustomResponse) => {
export const updateRequestBeforeAcknowledgedController = async (req: Request, res: CustomResponse, next: NextFunction) => {
const type = req.body.type;

switch(type){
case REQUEST_TYPE.OOO:
await acknowledgeOooRequest(req as AcknowledgeOooRequest, res as OooRequestResponse, next);
break;
case REQUEST_TYPE.ONBOARDING:
await updateOnboardingExtensionRequestController(req as UpdateOnboardingExtensionRequest, res as OnboardingExtensionResponse);
break;
Expand Down
32 changes: 32 additions & 0 deletions middlewares/conditionalOooChecks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { NextFunction } from "express";
import { CustomRequest, CustomResponse } from "../types/global";
import { REQUEST_TYPE } from "../constants/requests";
import { devFlagMiddleware } from "./devFlag";
import authorizeRoles from "./authorizeRoles";
const { SUPERUSER } = require("../constants/roles");

/**
* Conditional middleware that applies devFlag and superuser checks only for OOO requests.
* This allows onboarding requests to bypass these checks while maintaining security for OOO operations.
*
* @param {CustomRequest} req - The request object
* @param {CustomResponse} res - The response object
* @param {NextFunction} next - The next middleware function
*/
export const conditionalOooChecks = (req: CustomRequest, res: CustomResponse, next: NextFunction) => {
const requestType = req.body?.type;


if (requestType === REQUEST_TYPE.OOO) {

devFlagMiddleware(req, res, (err: any) => {
if (err) return next(err);


authorizeRoles([SUPERUSER])(req, res, next);
});
} else {

next();
}
};
46 changes: 45 additions & 1 deletion middlewares/validators/oooRequests.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import joi from "joi";
import { NextFunction } from "express";
import { REQUEST_STATE, REQUEST_TYPE } from "../../constants/requests";
import { OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest";
import { AcknowledgeOooRequest, OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest";

export const createOooStatusRequestValidator = async (
req: OooRequestCreateRequest,
Expand Down Expand Up @@ -38,3 +38,47 @@ export const createOooStatusRequestValidator = async (

await schema.validateAsync(req.body, { abortEarly: false });
};

const schema = joi
.object()
.strict()
.keys({
comment: joi.string().optional()
.messages({
"string.empty": "comment cannot be empty",
}),
status: joi
.string()
.valid(REQUEST_STATE.APPROVED, REQUEST_STATE.REJECTED)
.required()
.messages({
"any.only": "status must be APPROVED or REJECTED",
}),
type: joi.string().equal(REQUEST_TYPE.OOO).required().messages({
"any.required": "type is required",
"any.only": "type must be OOO"
})
});

/**
* Middleware to validate the acknowledge Out-Of-Office (OOO) request payload.
*
* @param {AcknowledgeOooRequest} req - The request object containing the body to be validated.
* @param {OooRequestResponse} res - The response object used to send error responses if validation fails.
* @param {NextFunction} next - The next middleware function to call if validation succeeds.
* @returns {Promise<void>} Resolves or sends errors.
*/
export const acknowledgeOooRequest = async (
req: AcknowledgeOooRequest,
res: OooRequestResponse,
next: NextFunction
): Promise<void> => {
try {
await schema.validateAsync(req.body, { abortEarly: false });
next();
} catch (error) {
const errorMessages = error.details.map((detail) => detail.message);
logger.error(`Error while validating request payload : ${errorMessages}`);
return res.boom.badRequest(errorMessages);
}
};
20 changes: 15 additions & 5 deletions middlewares/validators/requests.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import joi from "joi";
import { NextFunction } from "express";
import { REQUEST_STATE, REQUEST_TYPE } from "../../constants/requests";
import { OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest";
import { createOooStatusRequestValidator } from "./oooRequests";
import { AcknowledgeOooRequest, OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest";
import { acknowledgeOooRequest, createOooStatusRequestValidator } from "./oooRequests";
import { createExtensionRequestValidator } from "./extensionRequestsv2";
import {createTaskRequestValidator} from "./taskRequests";
import { ExtensionRequestRequest, ExtensionRequestResponse } from "../../types/extensionRequests";
Expand Down Expand Up @@ -92,6 +92,10 @@ export const getRequestsMiddleware = async (req: OooRequestCreateRequest, res: O
.string()
.valid(REQUEST_STATE.APPROVED, REQUEST_STATE.PENDING, REQUEST_STATE.REJECTED)
.optional(),
status: joi
.string()
.valid(REQUEST_STATE.APPROVED, REQUEST_STATE.PENDING, REQUEST_STATE.REJECTED)
.optional(),
page: joi.number().integer().min(0).when("next", {
is: joi.exist(),
then: joi.forbidden().messages({
Expand Down Expand Up @@ -125,18 +129,24 @@ export const getRequestsMiddleware = async (req: OooRequestCreateRequest, res: O
/**
* Validates update requests based on their type.
*
* @param {UpdateOnboardingExtensionRequest} req - Request object.
* @param {UpdateOnboardingExtensionRequest | AcknowledgeOooRequest} req - Request object.
* @param {CustomResponse} res - Response object.
* @param {NextFunction} next - Next middleware if valid.
* @returns {Promise<void>} Resolves or sends errors.
*/
export const updateRequestValidator = async (
req: UpdateOnboardingExtensionRequest,
req: UpdateOnboardingExtensionRequest | AcknowledgeOooRequest,
res: CustomResponse,
next: NextFunction
): Promise<void> => {
const type = req.body.type;

switch (type) {
case REQUEST_TYPE.OOO:
await acknowledgeOooRequest(
req,
res as OooRequestResponse, next);
break;
case REQUEST_TYPE.ONBOARDING:
await updateOnboardingExtensionRequestValidator(
req,
Expand All @@ -145,4 +155,4 @@ export const updateRequestValidator = async (
default:
return res.boom.badRequest("Invalid type");
}
};
};
Loading
Loading