Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
96 changes: 82 additions & 14 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,
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 @@ -148,3 +158,61 @@ export const updateOooRequestController = async (req: UpdateRequest, res: Custom
return res.boom.badImplementation(ERROR_WHILE_UPDATING_REQUEST);
}
};

/**
* Acknowledges an Out-of-Office (OOO) request. Only available to superusers
* and currently restricted to dev mode.
*
* @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 dev = req.query.dev === "true";
if(!dev) return res.boom.notImplemented("Feature not implemented");

const isSuperuser = req.userData?.roles?.super_user;
if (!isSuperuser) {
return res.boom.forbidden(UNAUTHORIZED_TO_UPDATE_REQUEST);
}

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);
}
if (error.statusCode === 400) {
return res.boom.badRequest(error.message);
}

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
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);
}
};
16 changes: 11 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 @@ -125,18 +125,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 +151,4 @@ export const updateRequestValidator = async (
default:
return res.boom.badRequest("Invalid type");
}
};
};
Loading
Loading