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");

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,
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({
"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
11 changes: 4 additions & 7 deletions models/discordactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@

for (let i = 0; i < nicknameUpdateBatches.length; i++) {
const promises = [];
const usersStatusDocsBatch = nicknameUpdateBatches[i];

Check warning on line 555 in models/discordactions.js

View workflow job for this annotation

GitHub Actions / build (22.10.0)

Variable Assigned to Object Injection Sink
usersStatusDocsBatch.forEach((document) => {
const doc = document.data();
const userId = doc.userId;
Expand Down Expand Up @@ -1019,7 +1019,7 @@
usersMap.set(taskAssignee, {
tasksCount: 1,
latestProgressCount: dateGap + 1,
isActive: false,
isOOO: false,
});
}
const updateTasksIdMap = async () => {
Expand All @@ -1038,10 +1038,7 @@
const userIdChunks = chunks(Array.from(usersMap.keys()), FIRESTORE_IN_CLAUSE_SIZE);
const userStatusSnapshotPromise = userIdChunks.map(
async (userIdList) =>
await userStatusModel
.where("currentStatus.state", "==", userState.ACTIVE)
.where("userId", "in", userIdList)
.get()
await userStatusModel.where("currentStatus.state", "==", userState.OOO).where("userId", "in", userIdList).get()
);
const userDetailsPromise = userIdChunks.map(
async (userIdList) =>
Expand All @@ -1055,7 +1052,7 @@

userStatusChunks.forEach((userStatusList) =>
userStatusList.forEach((doc) => {
usersMap.get(doc.data().userId).isActive = true;
usersMap.get(doc.data().userId).isOOO = true;
})
);

Expand Down Expand Up @@ -1097,7 +1094,7 @@
const isDiscordMember = !!discordUserData;
const shouldAddRole =
userData.latestProgressCount === 0 &&
userData.isActive &&
!userData.isOOO &&
isDiscordMember &&
discordUserData.isDeveloper &&
!discordUserData.isMaven &&
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,
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;
}
}
48 changes: 46 additions & 2 deletions test/fixtures/oooRequest/oooRequest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { REQUEST_STATE, REQUEST_TYPE } from "../../../constants/requests";
import { UserStatus } from "../../../types/userStatus";

export const createOooStatusRequests = {
type: "OOO",
Expand All @@ -15,8 +16,36 @@ 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 validUserCurrentStatus = {
from: Date.now(),
until: Date.now() + 1 * 24 * 60 * 60 * 1000,
message: "",
state: "ACTIVE",
updatedAt: Date.now(),
};

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

export const invalidOooStatusRequests = {
Expand Down Expand Up @@ -129,3 +158,18 @@ export const updateOooStatusRequest = [
reason: "Approval granted.",
},
];

export const createOooRequests3 = {
from: Date.now() + 100000,
until: Date.now() + 200000,
type: "OOO",
requestedBy: "suraj-maity-1",
reason: "Out of office for personal emergency.",
status: REQUEST_STATE.PENDING
};

export const acknowledgeOooRequest = {
type: REQUEST_TYPE.OOO,
status: REQUEST_STATE.APPROVED,
comment: "OOO request approved as it's emergency."
};
Loading
Loading