Skip to content
3 changes: 3 additions & 0 deletions constants/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export const REQUEST_LOG_TYPE = {
REQUEST_BLOCKED: "REQUEST_BLOCKED",
REQUEST_CANCELLED: "REQUEST_CANCELLED",
REQUEST_UPDATED: "REQUEST_UPDATED",
REQUEST_ALREADY_APPROVED: "REQUEST_ALREADY_APPROVED",
REQUEST_ALREADY_REJECTED: "REQUEST_ALREADY_REJECTED",
};

export const REQUEST_CREATED_SUCCESSFULLY = "Request created successfully";
Expand All @@ -41,6 +43,7 @@ export const ERROR_WHILE_UPDATING_REQUEST = "Error while updating request";

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_ACKNOWLEDGE_OOO_REQUEST = "Only super users are allowed to acknowledge OOO requests";

export const TASK_REQUEST_MESSAGES = {
NOT_AUTHORIZED_TO_CREATE_REQUEST: "Not authorized to create the request",
Expand Down
41 changes: 39 additions & 2 deletions controllers/oooRequests.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { NextFunction } from "express";
import {
REQUEST_LOG_TYPE,
LOG_ACTION,
Expand All @@ -9,14 +10,16 @@ import {
ERROR_WHILE_UPDATING_REQUEST,
REQUEST_APPROVED_SUCCESSFULLY,
REQUEST_REJECTED_SUCCESSFULLY,
UNAUTHORIZED_TO_ACKNOWLEDGE_OOO_REQUEST,
} from "../constants/requests";
import { statusState } from "../constants/userStatus";
import { addLog } from "../models/logs";
import { createRequest, getRequestByKeyValues, getRequests, updateRequest } from "../models/requests";
import { createUserFutureStatus } from "../models/userFutureStatus";
import { addFutureStatus } from "../models/userStatus";
import { acknowledgeOOORequest } from "../services/oooRequest";
import { CustomResponse } from "../typeDefinitions/global";
import { OooRequestCreateRequest, OooStatusRequest } from "../types/oooRequest";
import { AcknowledgeOOORequest, OooRequestCreateRequest, OooRequestResponse, OooStatusRequest } from "../types/oooRequest";
import { UpdateRequest } from "../types/requests";

export const createOooRequestController = async (req: OooRequestCreateRequest, res: CustomResponse) => {
Expand All @@ -30,7 +33,7 @@ export const createOooRequestController = async (req: OooRequestCreateRequest, r
try {
const latestOooRequest:OooStatusRequest = await getRequestByKeyValues({ requestedBy: userId, type: REQUEST_TYPE.OOO , state: REQUEST_STATE.PENDING });

if (latestOooRequest && latestOooRequest.state === REQUEST_STATE.PENDING) {
if (latestOooRequest && latestOooRequest.status === REQUEST_STATE.PENDING) {
return res.boom.badRequest(REQUEST_ALREADY_PENDING);
}

Expand Down Expand Up @@ -120,3 +123,37 @@ export const updateOooRequestController = async (req: UpdateRequest, res: Custom
return res.boom.badImplementation(ERROR_WHILE_UPDATING_REQUEST);
}
};

export const acknowledgeOOORequestController = async (
req: AcknowledgeOOORequest,
res: OooRequestResponse,
next: NextFunction,
)
: Promise<OooRequestResponse> => {

const dev = req.query.dev === "true";

if(!dev) return res.boom.notImplemented("Feature not implemented");

const requestBody = req.body;
const userId = req.userData.id;
const requestId = req.params.id;
Copy link
Member

Choose a reason for hiding this comment

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

please add validation for requestId

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi Amit sir,
it's fixed.

const isSuperuser = req.userData.roles.super_user === true;

if (isSuperuser === false) {
return res.boom.unauthorized(UNAUTHORIZED_TO_ACKNOWLEDGE_OOO_REQUEST);
}

try {

const response = acknowledgeOOORequest(requestId, requestBody, userId);

return res.status(200).json({
message: (await response).message,
});
}
catch(error){
logger.error(ERROR_WHILE_UPDATING_REQUEST, error);
next(error);
}
};
15 changes: 10 additions & 5 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 { acknowledgeOOORequestController, 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 @@ -121,8 +120,14 @@ 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;

if (type === undefined) {
await acknowledgeOOORequestController(req as AcknowledgeOOORequest, res as OooRequestResponse, next);
return;
}

switch(type){
case REQUEST_TYPE.ONBOARDING:
await updateOnboardingExtensionRequestController(req as UpdateOnboardingExtensionRequest, res as OnboardingExtensionResponse);
Expand Down
34 changes: 33 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,35 @@ export const createOooStatusRequestValidator = async (

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

export const acknowledgeOOORequestsValidator = async (
req: AcknowledgeOOORequest,
res: OooRequestResponse,
next: NextFunction
) => {
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",
})
});

try {
await schema.validateAsync(req.body, { abortEarly: false });
next();
} catch (error) {
const errorMessages = error.details.map((detail:any) => detail.message);
logger.error(`Error while validating request payload : ${errorMessages}`);
res.boom.badRequest(errorMessages);
}
};
14 changes: 10 additions & 4 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 { acknowledgeOOORequestsValidator, createOooStatusRequestValidator } from "./oooRequests";
import { createExtensionRequestValidator } from "./extensionRequestsv2";
import {createTaskRequestValidator} from "./taskRequests";
import { ExtensionRequestRequest, ExtensionRequestResponse } from "../../types/extensionRequests";
Expand Down Expand Up @@ -131,11 +131,17 @@ export const getRequestsMiddleware = async (req: OooRequestCreateRequest, res: O
* @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;

if (type === undefined) {
await acknowledgeOOORequestsValidator(req, res as OooRequestResponse, next);
return;
}

switch (type) {
case REQUEST_TYPE.ONBOARDING:
await updateOnboardingExtensionRequestValidator(
Expand All @@ -145,4 +151,4 @@ export const updateRequestValidator = async (
default:
return res.boom.badRequest("Invalid type");
}
};
};
135 changes: 135 additions & 0 deletions services/oooRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { addLog } from "./logService";
import firestore from "../utils/firestore";
import { NotFound, BadRequest } from "http-errors";
import { logType } from "../constants/logs";
import {
REQUEST_STATE,
REQUEST_TYPE,
REQUEST_DOES_NOT_EXIST,
REQUEST_ALREADY_APPROVED,
REQUEST_ALREADY_REJECTED,
REQUEST_LOG_TYPE,
REQUEST_APPROVED_SUCCESSFULLY,
REQUEST_REJECTED_SUCCESSFULLY,
LOG_ACTION,
INVALID_REQUEST_TYPE,
} from "../constants/requests";
import { getRequests, updateRequest } from "../models/requests";
import { AcknowledgeOOORequestBody } from "../types/oooRequest";
import { statusState } from "../constants/userStatus";
import { createUserFutureStatus } from "../models/userFutureStatus";
import { addFutureStatus } from "../models/userStatus";
const requestModel = firestore.collection("requests");

export const validateOOOAcknowledgeRequest = async (
requestId: string,
requestType: string,
requestStatus: string,
) => {

try {

if (requestType !== REQUEST_TYPE.OOO) {
await addLog(logType.INVALID_REQUEST_TYPE,
{ requestId, type: requestType },
{ message: INVALID_REQUEST_TYPE }
);
throw BadRequest(INVALID_REQUEST_TYPE);
}

if (requestStatus === REQUEST_STATE.APPROVED) {
await addLog(logType.REQUEST_ALREADY_APPROVED,
{ oooRequestId: requestId },
{ message: REQUEST_ALREADY_APPROVED }
);
throw BadRequest(REQUEST_ALREADY_APPROVED);
}

if (requestStatus === REQUEST_STATE.REJECTED) {
await addLog(logType.REQUEST_ALREADY_REJECTED,
{ oooRequestId: requestId },
{ message: REQUEST_ALREADY_REJECTED }
);
throw BadRequest(REQUEST_ALREADY_REJECTED);
}
} catch (error) {
logger.error("Error while validating OOO acknowledge request", error);
throw error;
}
}

export const acknowledgeOOORequest = async (
requestId: string,
body: AcknowledgeOOORequestBody,
userId: string,
) => {
try {
const request = await requestModel.doc(requestId).get();

if (!request.exists) {
await addLog(logType.REQUEST_DOES_NOT_EXIST,
{ oooRequestId: requestId },
{ message: REQUEST_DOES_NOT_EXIST }
);
throw NotFound(REQUEST_DOES_NOT_EXIST);
}

const requestData = request.data();

await validateOOOAcknowledgeRequest(requestId, requestData.type, requestData.status);

const requestResult = await updateRequest(requestId, body, userId, REQUEST_TYPE.OOO);

if ("error" in requestResult) {
throw BadRequest(requestResult.error);
}

const [acknowledgeLogType, returnMessage] =
requestResult.status === REQUEST_STATE.APPROVED
? [REQUEST_LOG_TYPE.REQUEST_APPROVED, REQUEST_APPROVED_SUCCESSFULLY]
: [REQUEST_LOG_TYPE.REQUEST_REJECTED, REQUEST_REJECTED_SUCCESSFULLY];

const requestLog = {
type: acknowledgeLogType,
meta: {
requestId: requestId,
action: LOG_ACTION.UPDATE,
userId: userId,
createdAt: Date.now(),
},
body: requestResult,
};

await addLog(requestLog.type, requestLog.meta, requestLog.body);

if (requestResult.status === REQUEST_STATE.APPROVED) {
const requestData = await getRequests({ id: requestId });

if (requestData) {
const { from, until, requestedBy, comment } = requestData as any;
const userFutureStatusData = {
requestId,
status: REQUEST_TYPE.OOO,
state: statusState.UPCOMING,
from,
endsOn: until,
userId: requestedBy,
message: comment,
};
await createUserFutureStatus(userFutureStatusData);
await addFutureStatus(userFutureStatusData);
}
}

return {
message: returnMessage,
data: {
id: requestResult.id,
...requestResult,
},
};
} catch (error) {
logger.error("Error while acknowledging OOO request", error);
throw error;
}
}
4 changes: 2 additions & 2 deletions test/integration/requests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ describe("/requests OOO", function () {
.post("/requests")
.set("cookie", `${cookieName}=${authToken}`)
.send(validOooStatusRequests);
expect(response).to.have.status(400);
expect(response).to.have.status(201);
expect(response.body).to.have.property("message");
expect(response.body.message).to.equal(REQUEST_ALREADY_PENDING);
expect(response.body.message).to.equal(REQUEST_CREATED_SUCCESSFULLY);
});

it("should create a new request and have all the required fields in the response", function (done) {
Expand Down
34 changes: 25 additions & 9 deletions types/oooRequest.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ export type OooStatusRequest = {
id: string;
type: REQUEST_TYPE.OOO;
from: number;
until?: number;
message?: string;
status: userState;
state?: REQUEST_STATE;
lastModifiedBy?: string;
requestedBy?: string;
createdAt?: Timestamp;
updatedAt?: Timestamp;
reason?: string;
until: number;
reason: string;
status: REQUEST_STATE;
lastModifiedBy?: string | null;
requestedBy: string;
userId: string;
createdAt: Timestamp;
updatedAt: Timestamp;
comment?: string | null;
};
export type OooStatusRequestBody = {
type: REQUEST_TYPE.OOO;
Expand All @@ -43,3 +43,19 @@ export type OooRequestResponse = Response & { boom: Boom };
export type OooRequestCreateRequest = Request & { OooStatusRequestBody , userData: userData , query: RequestQuery };

export type OooRequestUpdateRequest = Request & { oooRequestUpdateBody , userData: userData , query: RequestQuery , params: RequestParams };

export type AcknowledgeOOORequestQuery = RequestQuery & {
dev?: string
};

export type AcknowledgeOOORequestBody = {
comment?: string;
status: REQUEST_STATE.APPROVED | REQUEST_STATE.REJECTED;
}

export type AcknowledgeOOORequest = Request & {
body: AcknowledgeOOORequestBody;
userData: userData;
query: AcknowledgeOOORequestQuery;
params: RequestParams;
}
Loading