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
2 changes: 2 additions & 0 deletions constants/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export const logType = {
INVALID_REQUEST_TYPE: "INVALID_REQUEST_TYPE",
PENDING_REQUEST_CAN_BE_UPDATED: "PENDING_REQUEST_CAN_BE_UPDATED",
INVALID_REQUEST_DEADLINE: "INVALID_REQUEST_DEADLINE",
USER_STATUS_NOT_FOUND: "USER_STATUS_NOT_FOUND",
OOO_STATUS_FOUND: "OOO_STATUS_FOUND",
...REQUEST_LOG_TYPE,
};

Expand Down
4 changes: 4 additions & 0 deletions constants/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const REQUEST_LOG_TYPE = {
REQUEST_BLOCKED: "REQUEST_BLOCKED",
REQUEST_CANCELLED: "REQUEST_CANCELLED",
REQUEST_UPDATED: "REQUEST_UPDATED",
PENDING_REQUEST_FOUND: "PENDING_REQUEST_FOUND",
};

export const REQUEST_CREATED_SUCCESSFULLY = "Request created successfully";
Expand All @@ -41,6 +42,9 @@ 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_CREATE_OOO_REQUEST = "Unauthorized to create OOO request";
export const USER_STATUS_NOT_FOUND = "User status not found";
export const OOO_STATUS_ALREADY_EXIST = "Your status is already OOO. Please cancel OOO to raise new one";

export const TASK_REQUEST_MESSAGES = {
NOT_AUTHORIZED_TO_CREATE_REQUEST: "Not authorized to create the request",
Expand Down
82 changes: 55 additions & 27 deletions controllers/oooRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,85 @@ import {
LOG_ACTION,
REQUEST_CREATED_SUCCESSFULLY,
ERROR_WHILE_CREATING_REQUEST,
REQUEST_ALREADY_PENDING,
REQUEST_STATE,
REQUEST_TYPE,
ERROR_WHILE_UPDATING_REQUEST,
REQUEST_APPROVED_SUCCESSFULLY,
REQUEST_REJECTED_SUCCESSFULLY,
UNAUTHORIZED_TO_CREATE_OOO_REQUEST,
REQUEST_ALREADY_PENDING,
USER_STATUS_NOT_FOUND,
OOO_STATUS_ALREADY_EXIST,
} from "../constants/requests";
import { statusState } from "../constants/userStatus";
import { logType } from "../constants/logs";
import { addLog } from "../models/logs";
import { createRequest, getRequestByKeyValues, getRequests, updateRequest } from "../models/requests";
import { getRequestByKeyValues, getRequests, updateRequest } from "../models/requests";
import { createUserFutureStatus } from "../models/userFutureStatus";
import { addFutureStatus } from "../models/userStatus";
import { getUserStatus, addFutureStatus } from "../models/userStatus";
import { createOooRequest, validateUserStatus } from "../services/oooRequest";
import { CustomResponse } from "../typeDefinitions/global";
import { OooRequestCreateRequest, OooStatusRequest } from "../types/oooRequest";
import { OooRequestCreateRequest, OooRequestResponse, OooStatusRequest } from "../types/oooRequest";
import { UpdateRequest } from "../types/requests";

export const createOooRequestController = async (req: OooRequestCreateRequest, res: CustomResponse) => {
/**
* 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.
*/
export const createOooRequestController = async (
req: OooRequestCreateRequest,
res: OooRequestResponse
): Promise<OooRequestResponse> => {

const requestBody = req.body;
const userId = req?.userData?.id;
const { id: userId, username } = req.userData;
const isUserPartOfDiscord = req.userData.roles.in_discord;
const dev = req.query.dev === "true";

if (!userId) {
return res.boom.unauthorized();
if (!dev) return res.boom.notImplemented("Feature not implemented");
Comment on lines +46 to +48
Copy link
Contributor

Choose a reason for hiding this comment

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

NIT: we should have checked this condition at the beginning it self

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll fix this.
Thank you.


if (!isUserPartOfDiscord) {
return res.boom.forbidden(UNAUTHORIZED_TO_CREATE_OOO_REQUEST);
}

try {
const latestOooRequest:OooStatusRequest = await getRequestByKeyValues({ requestedBy: userId, type: REQUEST_TYPE.OOO , state: REQUEST_STATE.PENDING });
const userStatus = await getUserStatus(userId);
const validationResponse = await validateUserStatus(userId, userStatus);

if (latestOooRequest && latestOooRequest.state === REQUEST_STATE.PENDING) {
return res.boom.badRequest(REQUEST_ALREADY_PENDING);
if (validationResponse) {
if (validationResponse.error === USER_STATUS_NOT_FOUND) {
return res.boom.notFound(validationResponse.error);
}
if (validationResponse.error === OOO_STATUS_ALREADY_EXIST) {
return res.boom.forbidden(validationResponse.error);
}
}

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

const requestLog = {
type: REQUEST_LOG_TYPE.REQUEST_CREATED,
meta: {
requestId: requestResult.id,
action: LOG_ACTION.CREATE,
userId: userId,
createdAt: Date.now(),
},
body: requestResult,
};
await addLog(requestLog.type, requestLog.meta, requestLog.body);
if (latestOooRequest) {
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);

return res.status(201).json({
message: REQUEST_CREATED_SUCCESSFULLY,
Copy link
Contributor

Choose a reason for hiding this comment

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

Is response data not required?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As discussed at the time of design document review with Yash and Tejas, we don't need response data. You can check the design document.

Thank you

data: {
id: requestResult.id,
...requestResult,
},
});
} catch (err) {
logger.error(ERROR_WHILE_CREATING_REQUEST, err);
Expand Down
12 changes: 6 additions & 6 deletions middlewares/validators/oooRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ export const createOooStatusRequestValidator = async (
"number.min": "until date must be greater than or equal to from date",
})
.required(),
message: joi.string().required().messages({
"any.required": "message is required",
"string.empty": "message cannot be empty",
reason: joi.string().required().messages({
Copy link
Contributor

Choose a reason for hiding this comment

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

So here we have changed "message" to "reason", and also "state" to "type"

How are we handling the old request that are already created till now?

Will this affect how we are storing things in db

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 @prakashchoudhary07,
Yes, we've updated the field name from "message" to "reason", and "state" has been changed to "status", not "type".

We're not currently using the /requests endpoint to submit OOO requests; it exists solely on the backend.
There are only a few entries in the existing requests in production and staging. We discussed this with Ankush, and as those with access to the production and staging databases can make the necessary changes, he suggested updating them manually due to the limited number of OOO requests.

Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

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

@surajmaity1 Please create an issue for it, attach the link here, and track it. So that it won't slip through the gaps

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here is the issue ticket - #2418

Thank you!

"any.required": "reason is required",
"string.empty": "reason cannot be empty",
}),
state: joi.string().valid(REQUEST_STATE.PENDING).required().messages({
"any.only": "state must be PENDING",
type: joi.string().valid(REQUEST_TYPE.OOO).required().messages({
"string.empty": "type cannot be empty",
"any.required": "type is required",
}),
type: joi.string().valid(REQUEST_TYPE.OOO).required(),
});

await schema.validateAsync(req.body, { abortEarly: false });
Expand Down
95 changes: 95 additions & 0 deletions services/oooRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { logType } from "../constants/logs";
import {
LOG_ACTION,
OOO_STATUS_ALREADY_EXIST,
REQUEST_LOG_TYPE,
REQUEST_STATE,
USER_STATUS_NOT_FOUND,
} from "../constants/requests";
import { userState } from "../constants/userStatus";
import { createRequest } from "../models/requests";
import { OooStatusRequest, OooStatusRequestBody } from "../types/oooRequest";
import { UserStatus } from "../types/userStatus";
import { addLog } from "./logService";

/**
* Validates the user status.
*
* @param {string} userId - The unique identifier of the user.
* @param {UserStatus} userStatus - The status object of the user.
* @throws {Error} Throws an error if an issue occurs during validation.
*/
export const validateUserStatus = async (
userId: string,
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is this function being used apart from this file?
Can we please not export it, if this function is specific to this file only?

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 @prakashchoudhary07 ,
I'm exporting the validateUserStatus function so that we can properly test its behavior and ensure it's working as expected.

Thank you.

userStatus: UserStatus
) => {
try {

if (!userStatus.userStatusExists) {
await addLog(logType.USER_STATUS_NOT_FOUND, { userId }, { message: USER_STATUS_NOT_FOUND });
return {
error: USER_STATUS_NOT_FOUND
};
}

if (userStatus.data.currentStatus.state === userState.OOO) {
await addLog(logType.OOO_STATUS_FOUND,
{ userId, userStatus: userState.OOO },
{ message: OOO_STATUS_ALREADY_EXIST }
);
return {
error: OOO_STATUS_ALREADY_EXIST
};
}
} catch (error) {
logger.error("Error while validating OOO create request", error);
throw error;
}
}

/**
* Create an OOO request for a user.
*
* @param {OooStatusRequestBody} body - The request body containing OOO details.
* @param {string} username - The username of the person creating the request.
* @param {string} userId - The unique identifier of the user.
* @returns {Promise<object>} The created OOO request.
* @throws {Error} Throws an error if an issue occurs during validation.
*/
export const createOooRequest = async (
body: OooStatusRequestBody,
username: string,
userId: string
) => {
try {
const request: OooStatusRequest = await createRequest({
from: body.from,
until: body.until,
type: body.type,
requestedBy: username,
userId,
reason: body.reason,
comment: null,
status: REQUEST_STATE.PENDING,
lastModifiedBy: null,
});

const requestLog = {
type: REQUEST_LOG_TYPE.REQUEST_CREATED,
meta: {
requestId: request.id,
action: LOG_ACTION.CREATE,
userId,
createdAt: Date.now(),
},
body: request,
};

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

return request;
} catch (error) {
logger.error("Error while creating OOO request", error);
throw error;
}
}
43 changes: 21 additions & 22 deletions test/fixtures/oooRequest/oooRequest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { REQUEST_STATE, REQUEST_TYPE } from "../../../constants/requests";
// import { UserStatus } from "../../../types/userStatus";
import { UserStatus } from "../../../types/userStatus";

export const createOooStatusRequests = {
type: "OOO",
Expand All @@ -16,22 +16,21 @@ export const validOooStatusRequests = {
type: "OOO",
from: Date.now() + 1 * 24 * 60 * 60 * 1000,
until: Date.now() + 5 * 24 * 60 * 60 * 1000,
message: "Out of office for personal reasons.",
state: REQUEST_STATE.PENDING,
reason: "Out of office for personal reasons."
};

// export const createdOOORequest = {
// id: "Js7JnT6uRBLjGvSJM5X5",
// type: validOooStatusRequests.type,
// from: validOooStatusRequests.from,
// until: validOooStatusRequests.until,
// reason: validOooStatusRequests.reason,
// status: "PENDING",
// lastModifiedBy: null,
// requestedBy: "suraj-maity-1",
// userId: "jCqqOYCnm93mcmaYuSsQ",
// comment: null
// };
export const createdOOORequest = {
id: "Js7JnT6uRBLjGvSJM5X5",
type: validOooStatusRequests.type,
from: validOooStatusRequests.from,
until: validOooStatusRequests.until,
reason: validOooStatusRequests.reason,
status: "PENDING",
lastModifiedBy: null,
requestedBy: "suraj-maity-1",
userId: "jCqqOYCnm93mcmaYuSsQ",
comment: null
};

export const validUserCurrentStatus = {
from: Date.now(),
Expand All @@ -41,13 +40,13 @@ export const validUserCurrentStatus = {
updatedAt: Date.now(),
};

// export const testUserStatus: UserStatus = {
// id: "wcl0ZLsnngKUNZY9GkCo",
// data: {
// currentStatus: validUserCurrentStatus
// },
// userStatusExists: true
// };
export const testUserStatus: UserStatus = {
id: "wcl0ZLsnngKUNZY9GkCo",
data: {
currentStatus: validUserCurrentStatus
},
userStatusExists: true
};

export const invalidOooStatusRequests = {
type: "OOO",
Expand Down
Loading
Loading